@pi-r/redis 0.10.3 → 0.11.1

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2024 An Pham
1
+ Copyright 2025 An Pham
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
package/client/index.js CHANGED
@@ -1,58 +1,37 @@
1
- "use strict";
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;
1
+ 'use strict';
2
+
3
+ var node_crypto = require('node:crypto');
4
+ var types = require('@e-mc/types');
5
+ var util = require('@e-mc/db/util');
6
+
7
7
  const redis = require("redis");
8
- const node_crypto_1 = require("node:crypto");
9
8
  const Db = require("@e-mc/db");
10
- const types_1 = require("@e-mc/types");
11
- const util_1 = require("@e-mc/db/util");
12
9
  const DbPool = require("@pi-r/redis/client/pool");
13
10
  const POOL_STATE = {};
14
- async function doScan(client, key, index, cursor = [0], iterations = [Infinity], options) {
15
- if (!Array.isArray(cursor)) {
16
- cursor = [cursor];
17
- }
11
+ async function doScan(client, key, index, cursor = [], iterations = [], options) {
12
+ const target = (Array.isArray(cursor) ? cursor : [cursor]).map(item => Buffer.isBuffer(item) ? item : item.toString());
18
13
  if (!Array.isArray(iterations)) {
19
14
  iterations = [iterations];
20
15
  }
21
- let result = [], current = cursor[index] ?? 0, length = iterations[index] ?? Infinity;
16
+ let result = [], current = target[index] ?? '0', length = iterations[index] ?? Infinity;
22
17
  do {
23
18
  const item = await client.hScan(key, current, options);
24
- result = result.concat(item.tuples);
19
+ result = result.concat(item.entries);
25
20
  current = item.cursor;
26
- } while (current > 0 && --length > 0);
21
+ } while (+current > 0 && --length > 0);
27
22
  return result;
28
23
  }
29
- async function getClient(db, options, poolKey) {
30
- const socket = options.socket ||= {};
31
- if (!poolKey) {
32
- socket.keepAlive = false;
33
- }
34
- else if (typeof socket.keepAlive !== 'number') {
35
- socket.keepAlive = undefined;
36
- }
37
- const client = db.createClient(options);
38
- if (poolKey) {
39
- client.on('end', () => {
40
- delete POOL_STATE[poolKey];
41
- });
42
- }
43
- await client.connect();
44
- return client;
45
- }
24
+ const convertFloat = (value) => typeof value === 'number' || types.isString(value) && !isNaN(value = +value) ? value : undefined;
46
25
  async function setCredential(item) {
47
26
  const credential = this.getCredential(item);
48
27
  const options = item.options ||= {};
49
- const client = (0, types_1.isPlainObject)(options.client) ? options.client : options.client = {};
28
+ const client = types.isPlainObject(options.client) ? options.client : options.client = {};
50
29
  let { uri, username, password, database = 0 } = item;
51
30
  if (database > 0) {
52
31
  client.database = database;
53
32
  }
54
33
  if (credential) {
55
- const auth = (0, util_1.parseServerAuth)(credential, 6379);
34
+ const auth = util.parseServerAuth(credential, 6379);
56
35
  if (auth.database && (database = +auth.database) > 0) {
57
36
  client.database ||= database;
58
37
  }
@@ -64,7 +43,7 @@ async function setCredential(item) {
64
43
  delete item.credential;
65
44
  }
66
45
  if (!uri) {
67
- throw (0, types_1.errorMessage)("redis", "Invalid credentials", 'url');
46
+ throw types.errorMessage("redis", "Invalid credentials", 'url');
68
47
  }
69
48
  if (client.socket?.tls) {
70
49
  this.readTLSConfig(client.socket);
@@ -83,8 +62,8 @@ async function setCredential(item) {
83
62
  let pool;
84
63
  username = undefined;
85
64
  password = undefined;
86
- if ((0, types_1.isString)(usePool)) {
87
- username = client.username || (0, util_1.parseConnectionString)(uri)?.username;
65
+ if (types.isString(usePool)) {
66
+ username = client.username || util.parseConnectionString(uri)?.username;
88
67
  if (username) {
89
68
  [password, pool] = DbPool.validateKey(POOL_STATE, username, usePool);
90
69
  if (pool) {
@@ -98,17 +77,17 @@ async function setCredential(item) {
98
77
  }
99
78
  }
100
79
  const config = this.getPoolConfig("redis", password);
101
- const poolOptions = client.isolationPoolOptions ||= {};
80
+ const poolOptions = {};
102
81
  if (config) {
103
82
  const { min, max, timeout } = config;
104
83
  if (min >= 0) {
105
- poolOptions.min ??= min;
84
+ poolOptions.minimum ??= min;
106
85
  }
107
86
  if (max > 0) {
108
- poolOptions.max ??= max;
87
+ poolOptions.maximum ??= max;
109
88
  }
110
89
  if (timeout > 0) {
111
- poolOptions.acquireTimeoutMillis ??= timeout;
90
+ poolOptions.acquireTimeout ??= timeout;
112
91
  }
113
92
  }
114
93
  const poolKey = DbPool.asString(client);
@@ -117,13 +96,16 @@ async function setCredential(item) {
117
96
  return;
118
97
  }
119
98
  try {
120
- const instance = new DbPool(await getClient(redis, client, poolKey), poolKey, username && password ? { username, password } : undefined).add(item);
99
+ const clientPool = redis.createClientPool(client, poolOptions);
100
+ clientPool.on('end', () => {
101
+ delete POOL_STATE[poolKey];
102
+ });
103
+ const instance = new DbPool(clientPool, poolKey, username && password ? { username, password } : undefined).add(item);
121
104
  instance.parent = POOL_STATE;
122
105
  instance.success = 1;
123
106
  }
124
107
  catch {
125
108
  item.usePool = false;
126
- delete client.isolationPoolOptions;
127
109
  }
128
110
  }
129
111
  async function executeQuery(item, options) {
@@ -135,7 +117,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
135
117
  return [];
136
118
  }
137
119
  let parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss;
138
- if ((0, types_1.isPlainObject)(options)) {
120
+ if (types.isPlainObject(options)) {
139
121
  ({ parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss } = options);
140
122
  }
141
123
  else {
@@ -157,25 +139,28 @@ async function executeBatchQuery(batch, options = '', outResult) {
157
139
  const caching = this.hasCache("redis", sessionKey);
158
140
  const tasks = new Array(length);
159
141
  const clients = [];
160
- let redisClient, redisCredential, redisCache = 0, onceCredential = connectOnce ? batch[0].options?.client : undefined;
142
+ const pools = [];
143
+ let redisClient, redisPool, redisCredential, onceCredential = connectOnce ? batch[0].options?.client : undefined;
161
144
  const getConnection = async (item, credential) => {
145
+ if (redisPool) {
146
+ return redisPool;
147
+ }
162
148
  item.transactionState = 64;
163
149
  let client;
164
150
  if (item.usePool) {
165
151
  const pool = DbPool.findKey(POOL_STATE, item.usePool, DbPool.asString(credential), ...connectOnce ? [item, batch[0]] : [item]);
166
152
  if (pool) {
167
- client = await pool.getConnection(credential);
153
+ pools.push(client = await pool.getConnection(credential));
154
+ if (connectOnce) {
155
+ redisPool = client;
156
+ }
168
157
  pool.connected = true;
169
158
  }
170
159
  }
171
160
  if (!client) {
172
- if (connectOnce && parallel) {
173
- const size = length - redisCache;
174
- if (size > 1) {
175
- (credential.isolationPoolOptions ||= {}).max = size;
176
- }
177
- }
178
- clients.push(client = await getClient(redis, credential));
161
+ client = redis.createClient(credential);
162
+ await client.connect();
163
+ clients.push(client);
179
164
  }
180
165
  if (connectOnce) {
181
166
  redisClient = client;
@@ -189,10 +174,9 @@ async function executeBatchQuery(batch, options = '', outResult) {
189
174
  }
190
175
  for (let i = 0; i < length; ++i) {
191
176
  const item = batch[i];
192
- const { source, key, format = 'HASH', search, aggregate, options: clientOptions = {}, ignoreCache } = item;
177
+ const { source, key, format = 'HASH', search, aggregate, streams, options: clientOptions = {}, ignoreCache } = item;
193
178
  let credential = (redisCredential || onceCredential), error;
194
- if (!credential && !(0, types_1.isPlainObject)(credential = clientOptions.client) && (error = (0, types_1.errorMessage)(source, "Invalid credentials")) || !((0, types_1.isString)(key) || (0, types_1.isArray)(key) || (0, types_1.isPlainObject)(search) && ((0, types_1.isPlainObject)(search.schema) || (0, types_1.isString)(search.query)) || (0, types_1.isPlainObject)(aggregate) && ((0, types_1.isPlainObject)(aggregate.schema) || (0, types_1.isString)(aggregate.query))) && (error = (0, types_1.errorMessage)(source, "Missing database query", item.uri))) {
195
- ++redisCache;
179
+ if (!credential && !types.isPlainObject(credential = clientOptions.client) && (error = types.errorMessage(source, "Invalid credentials")) || !(types.isString(key) || types.isArray(key) || types.isPlainObject(search) && (types.isPlainObject(search.schema) || types.isString(search.query)) || types.isPlainObject(aggregate) && (types.isPlainObject(aggregate.schema) || types.isString(aggregate.query))) && (error = types.errorMessage(source, "Missing database query", item.uri))) {
196
180
  if (this.handleFail(error, item, { errorQuery })) {
197
181
  if (!parallel) {
198
182
  tasks.length = 0;
@@ -209,21 +193,20 @@ async function executeBatchQuery(batch, options = '', outResult) {
209
193
  continue;
210
194
  }
211
195
  item.transactionState = 1;
212
- const command = (0, types_1.isPlainObject)(clientOptions.command) ? redis.commandOptions(clientOptions.command) : null;
213
196
  const uuidKey = credential.uuidKey ||= (onceCredential ? batch[0] : item).credential?.uuidKey;
214
- const targetObject = typeof checkObject === 'string' ? this.hasCoerce("redis", 'options', uuidKey) && (0, types_1.asFunction)(checkObject) : checkObject;
197
+ const targetObject = typeof checkObject === 'string' ? this.hasCoerce("redis", 'options', uuidKey) && types.asFunction(checkObject) : checkObject;
215
198
  const cacheValue = ignoreCache === undefined ? sessionKey : Array.isArray(ignoreCache) ? { sessionKey, exclusiveOf: ignoreCache } : { sessionKey, renewCache: ignoreCache === 0 };
216
- if (command) {
217
- command.signal ||= this.signal;
218
- }
219
199
  let queryString = '', rows;
220
200
  if (caching && ignoreCache !== true) {
221
- if ((0, types_1.isObject)(search)) {
201
+ if (types.isObject(search)) {
222
202
  queryString = Db.asString(search, true);
223
203
  }
224
- else if ((0, types_1.isObject)(aggregate)) {
204
+ else if (types.isObject(aggregate)) {
225
205
  queryString = Db.asString(aggregate, true);
226
206
  }
207
+ else if (streams) {
208
+ queryString = Db.asString(streams, true);
209
+ }
227
210
  else if (!targetObject) {
228
211
  queryString = Db.asString(key, true);
229
212
  }
@@ -236,7 +219,6 @@ async function executeBatchQuery(batch, options = '', outResult) {
236
219
  if (ignoreCache !== 1) {
237
220
  rows = this.getQueryResult(source, DbPool.sanitize(credential), queryString, cacheValue);
238
221
  if (rows) {
239
- ++redisCache;
240
222
  if (parallel) {
241
223
  tasks[i] = Promise.resolve(rows);
242
224
  }
@@ -264,7 +246,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
264
246
  tasks[i] = new Promise(async (resolve, reject) => {
265
247
  let commandType;
266
248
  try {
267
- const client = redisClient || await getConnection(item, credential);
249
+ let client = redisClient || await getConnection(item, credential);
268
250
  const a = (arg) => typeof arg === 'string' || Buffer.isBuffer(arg) || typeof arg === 'number';
269
251
  const b = (arg) => typeof arg === 'number' || Buffer.isBuffer(arg) ? arg.toString() : arg;
270
252
  if (item.update) {
@@ -272,61 +254,72 @@ async function executeBatchQuery(batch, options = '', outResult) {
272
254
  const values = (!Array.isArray(item.update) ? [item.update] : item.update).map(async (target) => {
273
255
  return new Promise(async (success, failed) => {
274
256
  let { key: k, value: v, format: f, NX, XX, options: setOptions = {} } = target;
275
- f = ((0, types_1.isString)(f) ? f.toUpperCase() : 'HASH');
257
+ f = (types.isString(f) ? f.toUpperCase() : 'HASH');
276
258
  if (v === undefined && f !== 'JSON') {
277
259
  success(target);
278
260
  return;
279
261
  }
280
262
  const has = (n) => typeof n === 'number' && n > 0;
281
- const failKey = () => {
282
- failed((0, types_1.errorMessage)(f, "Invalid key", Db.asString(k) || "Unknown"));
283
- };
284
- const failValue = () => {
285
- failed((0, types_1.errorMessage)(f, "Invalid value", Db.asString(v) || "Unknown"));
286
- };
287
- const commandSet = (0, types_1.isPlainObject)(setOptions.command) && redis.commandOptions(setOptions.command);
263
+ const failKey = () => failed(types.errorMessage(f, "Invalid key", Db.asString(k) || "Unknown"));
264
+ const failValue = () => failed(types.errorMessage(f, "Invalid value", Db.asString(v) || "Unknown"));
265
+ if (types.isPlainObject(setOptions.command)) {
266
+ client = client.withCommandOptions(setOptions.command);
267
+ }
288
268
  let pending, codeMin = 0, EX, PX, EXAT, PXAT, KEEPTTL;
289
269
  if (f === 'JSON') {
290
270
  if (typeof k !== 'string') {
291
271
  failKey();
292
272
  return;
293
273
  }
294
- if (this.hasCoerce("redis", 'options', uuidKey) && (0, types_1.isString)(v) && v.startsWith('new')) {
295
- ({ outV: v } = (0, types_1.coerceObject)({ outV: v }));
296
- }
297
- let { path = '$', command: cmd, start, stop, index } = target;
298
- const name = ((0, types_1.isString)(cmd) ? cmd.toUpperCase() : 'SET');
299
- if (Array.isArray(v) && name !== 'ARRAPPEND' && name !== 'ARRINSERT' && name !== 'MSET') {
300
- v = v[0];
274
+ if (this.hasCoerce("redis", 'options', uuidKey) && types.isString(v) && v.startsWith('new')) {
275
+ ({ outV: v } = types.coerceObject({ outV: v }));
301
276
  }
277
+ const { path = '$', command: cmd, start, stop, index } = target;
278
+ const name = (types.isString(cmd) ? cmd.toUpperCase() : 'SET');
302
279
  switch (name) {
303
280
  case 'ARRAPPEND':
304
- pending = commandSet ? client.json.arrAppend(commandSet, k, path, ...(Array.isArray(v) ? v : [v])) : client.json.arrAppend(k, path, ...(Array.isArray(v) ? v : [v]));
281
+ case 'ARRINSERT': {
282
+ let v1, v2;
283
+ if (types.isArray(v)) {
284
+ v1 = v[0];
285
+ v2 = v.slice(1);
286
+ }
287
+ else {
288
+ v1 = v;
289
+ v2 = [];
290
+ }
291
+ if (name === 'ARRAPPEND') {
292
+ pending = client.json.arrAppend(k, path, v1, ...v2);
293
+ }
294
+ else if (typeof index === 'number') {
295
+ pending = client.json.arrInsert(k, path, index, v1, ...v2);
296
+ }
305
297
  break;
306
- case 'ARRINDEX':
298
+ }
299
+ case 'ARRINDEX': {
300
+ const s1 = convertFloat(start);
307
301
  codeMin = -1;
308
- pending = commandSet ? client.json.arrIndex(commandSet, k, path, v, start, stop) : client.json.arrIndex(k, path, v, start, stop);
309
- break;
310
- case 'ARRINSERT':
311
- if (typeof index === 'number' || (0, types_1.isString)(index) && !isNaN(index = parseInt(index))) {
312
- pending = commandSet ? client.json.arrInsert(commandSet, k, path, index, ...(Array.isArray(v) ? v : [v])) : client.json.arrInsert(k, path, index, ...(Array.isArray(v) ? v : [v]));
313
- }
302
+ pending = client.json.arrIndex(k, path, v, s1 !== undefined ? { range: { start: s1, stop: convertFloat(stop) } } : undefined);
314
303
  break;
304
+ }
315
305
  case 'ARRPOP':
316
306
  codeMin = -Infinity;
317
- pending = commandSet ? client.json.arrPop(commandSet, k, path, index) : client.json.arrPop(k, path, index);
307
+ pending = client.json.arrPop(k, { path, index: convertFloat(index) });
318
308
  break;
319
- case 'ARRTRIM':
320
- if ((typeof start === 'number' || (0, types_1.isString)(start) && !isNaN(start = parseInt(start))) && (typeof stop === 'number' || (0, types_1.isString)(stop) && !isNaN(stop = parseInt(stop)))) {
321
- pending = commandSet ? client.json.arrTrim(commandSet, k, path, start, stop) : client.json.arrTrim(k, path, start, stop);
309
+ case 'ARRTRIM': {
310
+ const s1 = convertFloat(start);
311
+ const s2 = convertFloat(stop);
312
+ if (s1 !== undefined && s2 !== undefined) {
313
+ pending = client.json.arrTrim(k, path, s1, s2);
322
314
  }
323
315
  break;
316
+ }
324
317
  case 'DEL':
325
318
  case 'FORGET':
326
- pending = commandSet ? client.json[name === 'DEL' ? 'del' : 'forget'](commandSet, k, path) : client.json[name === 'DEL' ? 'del' : 'forget'](k, path);
319
+ pending = client.json[name === 'DEL' ? 'del' : 'forget'](k, { path });
327
320
  break;
328
321
  case 'MERGE':
329
- pending = commandSet ? client.json.merge(commandSet, k, path, v) : client.json.merge(k, path, v);
322
+ pending = client.json.merge(k, path, v);
330
323
  break;
331
324
  case 'MSET': {
332
325
  const data = (Array.isArray(v) ? v : [v])
@@ -337,24 +330,26 @@ async function executeBatchQuery(batch, options = '', outResult) {
337
330
  return m;
338
331
  });
339
332
  if (data.length > 0) {
340
- pending = commandSet ? client.json.mSet(commandSet, data) : client.json.mSet(data);
333
+ pending = client.json.mSet(data);
341
334
  }
342
335
  break;
343
336
  }
344
337
  case 'NUMINCRBY':
345
- case 'NUMMULTBY':
346
- if (typeof v === 'number' || (0, types_1.isString)(v) && !isNaN(v = parseFloat(v))) {
347
- pending = commandSet ? client.json[name === 'NUMINCRBY' ? 'numIncrBy' : 'numMultBy'](commandSet, k, path, v) : client.json[name === 'NUMINCRBY' ? 'numIncrBy' : 'numMultBy'](k, path, v);
338
+ case 'NUMMULTBY': {
339
+ const n = convertFloat(v);
340
+ if (n !== undefined) {
341
+ pending = client.json[name === 'NUMINCRBY' ? 'numIncrBy' : 'numMultBy'](k, path, n);
348
342
  }
349
343
  break;
344
+ }
350
345
  case 'STRAPPEND':
351
- if ((0, types_1.isString)(v)) {
352
- pending = commandSet ? client.json.strAppend(commandSet, k, path, v) : client.json.strAppend(k, path, v);
346
+ if (types.isString(v)) {
347
+ pending = client.json.strAppend(k, v, { path });
353
348
  }
354
349
  break;
355
350
  default: {
356
351
  const flags = NX ? { NX } : XX ? { XX } : undefined;
357
- pending = commandSet ? client.json.set(commandSet, k, path, v, flags) : client.json.set(k, path, v, flags);
352
+ pending = client.json.set(k, path, v, flags);
358
353
  break;
359
354
  }
360
355
  }
@@ -362,39 +357,39 @@ async function executeBatchQuery(batch, options = '', outResult) {
362
357
  else if (typeof k === 'string' || Buffer.isBuffer(k)) {
363
358
  ({ EX, PX, EXAT, PXAT, KEEPTTL } = target);
364
359
  const field = target.field;
365
- if (field === undefined && field === null) {
360
+ if (field == null) {
366
361
  if (a(v)) {
367
362
  const flags = setOptions.set || {};
368
363
  if (NX) {
369
- flags.NX = true;
364
+ flags.condition = 'NX';
370
365
  }
371
366
  if (XX) {
372
- flags.XX = true;
367
+ flags.condition = 'XX';
373
368
  }
374
369
  if (has(EX)) {
375
- flags.EX = EX;
370
+ flags.expiration = { type: 'EX', value: EX };
376
371
  EX = 0;
377
372
  }
378
373
  if (has(PX)) {
379
- flags.PX = PX;
374
+ flags.expiration = { type: 'PX', value: PX };
380
375
  PX = 0;
381
376
  }
382
377
  if (has(EXAT)) {
383
- flags.EXAT = EXAT;
378
+ flags.expiration = { type: 'EXAT', value: EXAT };
384
379
  EXAT = 0;
385
380
  }
386
381
  if (has(PXAT)) {
387
- flags.PXAT = PXAT;
382
+ flags.expiration = { type: 'PXAT', value: PXAT };
388
383
  PXAT = 0;
389
384
  }
390
385
  if (KEEPTTL) {
391
- flags.KEEPTTL = true;
386
+ flags.expiration = { type: 'KEEPTTL' };
392
387
  KEEPTTL = false;
393
388
  }
394
389
  codeMin = NaN;
395
- pending = commandSet ? client.set(commandSet, k, v, flags) : client.set(k, v, flags);
390
+ pending = client.set(k, v, flags);
396
391
  }
397
- else if ((0, types_1.isObject)(v)) {
392
+ else if (types.isObject(v)) {
398
393
  let valid = true;
399
394
  if (NX || XX) {
400
395
  valid = await client.exists(k) === 1;
@@ -406,26 +401,26 @@ async function executeBatchQuery(batch, options = '', outResult) {
406
401
  success(target);
407
402
  return;
408
403
  }
409
- pending = commandSet ? client.hSet(commandSet, k, v) : client.hSet(k, v);
404
+ pending = client.hSet(k, v);
410
405
  }
411
406
  }
412
407
  else if (NX) {
413
408
  if (a(field) && a(v)) {
414
409
  codeMin = -1;
415
- pending = commandSet ? client.hSetNX(commandSet, k, b(field), b(v)) : client.hSetNX(k, b(field), b(v));
410
+ pending = client.hSetNX(k, b(field), b(v));
416
411
  }
417
412
  }
418
413
  else {
419
- let valid = true;
420
- if (XX) {
421
- valid = a(k) && a(field) && await client.hExists(b(k), b(field));
414
+ let valid = 1;
415
+ if (XX && a(k) && a(field)) {
416
+ valid = await client.hExists(b(k), b(field));
422
417
  }
423
418
  if (a(field) && a(v)) {
424
- if (!valid) {
419
+ if (valid === 0) {
425
420
  success(target);
426
421
  return;
427
422
  }
428
- pending = commandSet ? client.hSet(commandSet, k, field, v) : client.hSet(k, field, v);
423
+ pending = client.hSet(k, field, v);
429
424
  }
430
425
  }
431
426
  }
@@ -435,7 +430,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
435
430
  }
436
431
  if (pending) {
437
432
  pending.then(async (code) => {
438
- if (code === undefined || code === null || code !== -Infinity && (0, types_1.isString)(code) && code !== 'OK' || typeof code === 'number' && code <= codeMin || Array.isArray(code) && code.every(resp => resp === null || typeof resp === 'number' && resp <= codeMin)) {
433
+ if (code == null || code !== -Infinity && types.isString(code) && code !== 'OK' || typeof code === 'number' && code <= codeMin || Array.isArray(code) && code.every(resp => resp === null || typeof resp === 'number' && resp <= codeMin)) {
439
434
  failValue();
440
435
  return;
441
436
  }
@@ -473,7 +468,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
473
468
  success(target);
474
469
  }
475
470
  else {
476
- failed((0, types_1.errorMessage)(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
471
+ failed(types.errorMessage(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
477
472
  }
478
473
  })
479
474
  .catch(failed);
@@ -504,26 +499,31 @@ async function executeBatchQuery(batch, options = '', outResult) {
504
499
  }
505
500
  });
506
501
  }
502
+ const commandOptions = credential.commandOptions || clientOptions.command;
503
+ if (types.isPlainObject(commandOptions)) {
504
+ commandOptions.abortSignal ||= this.signal;
505
+ client = client.withCommandOptions(commandOptions);
506
+ }
507
507
  commandType = this.commandType.SELECT;
508
- const target = (0, types_1.isObject)(search) ? search : (0, types_1.isObject)(aggregate) ? aggregate : null;
508
+ const target = types.isObject(search) ? search : types.isObject(aggregate) ? aggregate : null;
509
509
  if (target) {
510
510
  const { query = '', schema, index } = target;
511
511
  let idx = '';
512
- if ((0, types_1.isObject)(schema)) {
513
- idx = index || (0, node_crypto_1.randomUUID)();
514
- await (command ? client.ft.create(command, idx, schema, target.options) : client.ft.create(idx, schema, target.options));
512
+ if (types.isObject(schema)) {
513
+ idx = index || node_crypto.randomUUID();
514
+ await client.ft.create(idx, schema, target.options);
515
515
  }
516
516
  try {
517
517
  if (target === search) {
518
- const reply = await (command ? client.ft.search(command, index || idx, query, clientOptions.search) : client.ft.search(index || idx, query, clientOptions.search));
518
+ const reply = await client.ft.search(index || idx, query, clientOptions.search);
519
519
  rows = reply.documents.map(doc => (doc.value.__id__ = doc.id) && doc.value);
520
520
  }
521
521
  else {
522
- ({ results: rows } = await (command ? client.ft.aggregate(command, index || idx, query, clientOptions.aggregate) : client.ft.aggregate(index || idx, query, clientOptions.aggregate)));
522
+ ({ results: rows } = await client.ft.aggregate(index || idx, query, clientOptions.aggregate));
523
523
  }
524
524
  }
525
525
  catch (err) {
526
- this.addLog(this.statusType.WARN, err, { source: target === search ? 'FT.SEARCH' : 'FT.AGGREGATE' });
526
+ this.addLog(3, err, { source: target === search ? 'FT.SEARCH' : 'FT.AGGREGATE' });
527
527
  if (err instanceof Error) {
528
528
  throw err;
529
529
  }
@@ -536,58 +536,70 @@ async function executeBatchQuery(batch, options = '', outResult) {
536
536
  }
537
537
  }
538
538
  }
539
+ else if (streams) {
540
+ const data = await client.xRead(streams, clientOptions.xread);
541
+ if (data) {
542
+ rows = data;
543
+ }
544
+ }
539
545
  else if (key) {
540
546
  let data;
541
547
  if (Array.isArray(key)) {
542
548
  switch (format.toUpperCase()) {
543
549
  case 'HKEYS':
544
- data = await Promise.all(key.map(async (k) => client.hKeys(...command ? [command, k] : [k])));
550
+ data = await Promise.all(key.map(async (k) => client.hKeys(k)));
545
551
  break;
546
552
  case 'HVALS':
547
- data = await Promise.all(key.map(async (k) => client.hVals(...command ? [command, k] : [k])));
553
+ data = await Promise.all(key.map(async (k) => client.hVals(k)));
548
554
  break;
549
555
  case 'HSCAN':
550
556
  data = await Promise.all(key.map(async (k, index) => doScan(client, k, index, item.cursor, item.iterations, clientOptions.scan)));
551
557
  break;
558
+ case 'SMEMBERS':
559
+ data = await Promise.all(key.map(async (k) => client.sMembers(k)));
560
+ break;
552
561
  case 'JSON':
553
- 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();
562
+ data = (await client.json.mGet(key.map(c => b(c)), item.path || '$')).flat();
554
563
  break;
555
564
  default:
556
- data = await client.mGet(...command ? [command, key] : [key]);
565
+ data = await client.mGet(key);
557
566
  break;
558
567
  }
559
568
  }
560
569
  else {
561
570
  switch (format.toUpperCase()) {
562
571
  case 'HKEYS':
563
- data = await client.hKeys(...command ? [command, key] : [key]);
572
+ data = await client.hKeys(key);
564
573
  break;
565
574
  case 'HVALS':
566
- data = await client.hVals(...command ? [command, key] : [key]);
575
+ data = await client.hVals(key);
567
576
  break;
568
577
  case 'HSCAN':
569
578
  data = await doScan(client, key, 0, item.cursor, item.iterations, clientOptions.scan);
570
579
  break;
580
+ case 'SMEMBERS':
581
+ data = await client.sMembers(key);
582
+ break;
571
583
  case 'JSON':
572
- 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)]));
584
+ data = await client.json.get(b(key), clientOptions.get);
573
585
  break;
574
586
  default:
575
587
  if (Array.isArray(item.field)) {
576
- data = await client.hmGet(...command ? [command, key, item.field] : [key, item.field]);
588
+ data = await client.hmGet(key, item.field);
577
589
  }
578
590
  else if (item.field) {
579
- data = await client.hGet(...command ? [command, key, item.field] : [key, item.field]);
591
+ data = await client.hGet(key, item.field);
580
592
  }
581
593
  else {
582
- data = await client.hGetAll(...command ? [command, key] : [key]);
594
+ data = await client.hGetAll(key);
583
595
  }
584
596
  break;
585
597
  }
586
598
  }
587
599
  rows = (targetObject ? targetObject(item, data) : data);
588
600
  }
589
- if (rows === undefined) {
590
- throw (0, types_1.errorMessage)(source, "Missing database query");
601
+ if (rows == null) {
602
+ throw types.errorMessage(source, "Missing database query");
591
603
  }
592
604
  this.add(item, 4);
593
605
  resolve(this.setQueryResult(source, DbPool.sanitize(redisCredential || credential), queryString, rows, cacheValue));
@@ -623,11 +635,14 @@ async function executeBatchQuery(batch, options = '', outResult) {
623
635
  }
624
636
  }
625
637
  }
626
- return this.processRows(batch, tasks, clients.length === 0 ? parallel : {
638
+ return this.processRows(batch, tasks, {
627
639
  parallel,
628
640
  disconnect() {
629
641
  for (const item of clients) {
630
- void item.disconnect();
642
+ item.destroy();
643
+ }
644
+ for (const item of pools) {
645
+ void item.close();
631
646
  }
632
647
  }
633
648
  }, outResult);
@@ -635,5 +650,12 @@ async function executeBatchQuery(batch, options = '', outResult) {
635
650
  async function checkTimeout(value, limit = 0) {
636
651
  return DbPool.checkTimeout(POOL_STATE, value, limit);
637
652
  }
638
- exports.DB_SOURCE_CLIENT = true;
639
- exports.DB_SOURCE_TYPE = types_1.DB_TYPE.NOSQL | types_1.DB_TYPE.KEYVALUE;
653
+ const DB_SOURCE_CLIENT = true;
654
+ const DB_SOURCE_TYPE = types.DB_TYPE.NOSQL | types.DB_TYPE.KEYVALUE;
655
+
656
+ exports.DB_SOURCE_CLIENT = DB_SOURCE_CLIENT;
657
+ exports.DB_SOURCE_TYPE = DB_SOURCE_TYPE;
658
+ exports.checkTimeout = checkTimeout;
659
+ exports.executeBatchQuery = executeBatchQuery;
660
+ exports.executeQuery = executeQuery;
661
+ exports.setCredential = setCredential;
package/client/pool.d.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import type { DbPoolConstructor } from '@e-mc/types/lib/db';
2
2
 
3
- import type { DbPoolCredential, RedisDataSource } from '../types';
3
+ import type { DbPoolCredential, RedisClientPoolInstance, RedisClientPoolType, RedisDataSource } from '../types';
4
4
 
5
- import type { RedisClientType } from 'redis';
6
-
7
- declare const RedisPool: DbPoolConstructor<RedisDataSource, RedisClientType, RedisClientType, DbPoolCredential>;
5
+ declare const RedisPool: DbPoolConstructor<RedisDataSource, RedisClientPoolInstance, RedisClientPoolType, DbPoolCredential>;
8
6
 
9
7
  export = RedisPool;
package/client/pool.js CHANGED
@@ -1,8 +1,7 @@
1
- "use strict";
2
1
  const DbPool = require('@e-mc/db/pool');
3
2
  const POOL_ACTIVE = new WeakSet();
4
3
  class RedisPool extends DbPool {
5
- static CACHE_IGNORE = ['modules', 'functions', 'scripts'];
4
+ static CACHE_IGNORE = ['modules', 'functions', 'scripts', 'credentialsProvider'];
6
5
  static asString(credential) {
7
6
  if (credential.url) {
8
7
  return JSON.stringify(credential);
@@ -16,8 +15,8 @@ class RedisPool extends DbPool {
16
15
  if (!POOL_ACTIVE.has(credential) || credential.uuidKey) {
17
16
  return credential;
18
17
  }
19
- if ('socket' in credential || 'isolationPoolOptions' in credential) {
20
- return { ...credential, socket: credential.socket?.tls ? { tls: true } : undefined, isolationPoolOptions: undefined };
18
+ if ('socket' in credential) {
19
+ return { ...credential, socket: credential.socket?.tls ? { tls: true } : undefined };
21
20
  }
22
21
  return credential;
23
22
  }
@@ -25,13 +24,13 @@ class RedisPool extends DbPool {
25
24
  if (credential) {
26
25
  POOL_ACTIVE.add(credential);
27
26
  }
28
- return this.client;
27
+ return this.client.connect();
29
28
  }
30
29
  async close() {
31
- return this.client.disconnect();
30
+ return this.client.close();
32
31
  }
33
32
  isEmpty() {
34
- return this.closed;
33
+ return this.closed || this.client.totalClients === 0;
35
34
  }
36
35
  get closed() {
37
36
  return !this.client.isOpen;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/redis",
3
- "version": "0.10.3",
3
+ "version": "0.11.1",
4
4
  "description": "Redis client driver for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "types": "client/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/anpham6/pi-r.git",
12
+ "url": "git+https://github.com/anpham6/pi-r2.git",
13
13
  "directory": "src/db/redis"
14
14
  },
15
15
  "keywords": [
@@ -20,8 +20,8 @@
20
20
  "license": "MIT",
21
21
  "homepage": "https://github.com/anpham6/pi-r#readme",
22
22
  "dependencies": {
23
- "@e-mc/db": "^0.12.7",
24
- "@e-mc/types": "^0.12.7",
25
- "redis": "^4.7.1"
23
+ "@e-mc/db": "^0.13.5",
24
+ "@e-mc/types": "^0.13.5",
25
+ "redis": "^5.10.0"
26
26
  }
27
27
  }
package/types/index.d.ts CHANGED
@@ -4,31 +4,37 @@ import type { IdentifierAction } from '@e-mc/types/lib/core';
4
4
  import type { CascadeAction, ServerAuth } from '@e-mc/types/lib/db';
5
5
  import type { AuthValue } from '@e-mc/types/lib/http';
6
6
 
7
- import type { RedisJSON } from '@redis/json/dist/commands';
8
- import type { ClientCommandOptions } from '@redis/client/dist/lib/client';
9
- import type { RedisCommandArgument } from '@redis/client/dist/lib/commands';
10
- import type { CommandOptions } from '@redis/client/dist/lib/command-options';
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';
14
- import type { RediSearchSchema, RedisClientOptions, SearchOptions, SetOptions } from 'redis';
7
+ import type { RedisClientOptions } from '@redis/client';
8
+ import type { RedisJSON } from '@redis/json/dist/lib/commands';
9
+ import type { RedisArgument } from '@redis/client/dist/lib/RESP/types';
10
+ import type { CommandOptions } from '@redis/client/dist/lib/client/commands-queue';
11
+ import type { ScanOptions } from '@redis/client/dist/lib/commands/SCAN';
12
+ import type { CreateOptions } from '@redis/search/dist/lib/commands/CREATE';
13
+ import type { FtAggregateOptions } from '@redis/search/dist/lib/commands/AGGREGATE';
14
+ import type { FtSearchOptions } from '@redis/search/dist/lib/commands/SEARCH';
15
+ import type { JsonGetOptions } from '@redis/json/dist/lib/commands/GET';
16
+ import type { XReadOptions, XReadStreams } from '@redis/client/dist/lib/commands/XREAD';
17
+ import type { RedisClientPoolType as IRedisClientPoolType, RediSearchSchema, RedisDefaultModules, RedisModules, SetOptions } from 'redis';
15
18
 
16
19
  export interface RedisDataSource extends DbDataSource<string, PlainObject, RedisSetValue | RedisSetValue[] | RedisJSONValue | RedisJSONValue[], RedisCredential, string>, CascadeAction, AuthValue {
17
20
  source: "redis";
18
- key?: RedisCommandArgument | RedisCommandArgument[];
19
- field?: RedisCommandArgument | string[];
21
+ key?: RedisArgument | RedisArgument[];
22
+ field?: RedisArgument | string[];
20
23
  path?: string;
21
- format?: RedisFormat | "HKEYS" | "HVALS" | "HSCAN";
24
+ format?: RedisFormat | "HKEYS" | "HVALS" | "HSCAN" | "SMEMBERS";
22
25
  search?: RedisQuery;
23
26
  aggregate?: RedisQuery;
24
- cursor?: number | number[];
27
+ streams?: XReadStreams;
28
+ cursor?: RedisArgument | RedisArgument[] | number | number[];
25
29
  iterations?: number | number[];
26
30
  options?: {
27
31
  client?: RedisClientOptions;
28
- command?: RedisCommandOptions;
29
- get?: PlainObject;
30
- search?: SearchOptions;
31
- aggregate?: AggregateOptions;
32
+ /** @deprecated client.commandOptions */
33
+ command?: CommandOptions;
34
+ get?: JsonGetOptions;
35
+ search?: FtSearchOptions;
36
+ aggregate?: FtAggregateOptions;
37
+ xread?: XReadOptions;
32
38
  scan?: ScanOptions;
33
39
  };
34
40
  database?: number;
@@ -46,11 +52,11 @@ export interface RedisCommand<T = "HASH" | "JSON" | undefined, U = unknown, V =
46
52
  options?: {
47
53
  set?: SetOptions;
48
54
  expire?: { [K in RedisExpireCondition]?: boolean; };
49
- command?: RedisCommandOptions;
55
+ command?: CommandOptions;
50
56
  };
51
57
  }
52
58
 
53
- export interface RedisSetValue extends RedisCommand<"HASH", RedisCommandArgument, RedisCommandValue | RedisHSETObject> {
59
+ export interface RedisSetValue extends RedisCommand<"HASH", RedisArgument, RedisCommandValue | RedisHSETObject> {
54
60
  field?: RedisCommandValue | RedisHSETObject;
55
61
  EX?: number;
56
62
  PX?: number;
@@ -67,25 +73,26 @@ export interface RedisJSONValue extends RedisCommand<"JSON", string, RedisJSON |
67
73
  index?: number | string;
68
74
  }
69
75
 
70
- export interface RedisQuery {
76
+ export interface RedisQuery<T = CreateOptions> {
71
77
  index?: string;
72
78
  schema?: RediSearchSchema | string;
73
79
  query?: string;
74
- options?: PlainObject;
80
+ options?: T;
75
81
  }
76
82
 
77
83
  export interface JsonMSetItem {
78
- key: RedisCommandArgument;
79
- path: RedisCommandArgument;
84
+ key: RedisArgument;
85
+ path: RedisArgument;
80
86
  value: RedisJSON;
81
87
  }
82
88
 
83
89
  export type RedisFormat = "HASH" | "JSON";
84
90
  export type RedisCommandJSON = "ARRAPPEND" | "ARRINDEX" | "ARRINSERT" | "ARRPOP" | "ARRTRIM" | "DEL" | "FORGET" | "MERGE" | "MSET" | "NUMINCRBY" | "NUMMULTBY" | "SET" | "STRAPPEND";
85
91
  export type RedisExpireCondition = "NX" | "XX" | "GT" | "LT";
86
- export type RedisCommandValue = RedisCommandArgument | number;
87
- export type RedisCommandOptions = CommandOptions<ClientCommandOptions>;
92
+ export type RedisCommandValue = RedisArgument | number;
93
+ export type RedisClientPoolType<T extends RedisModules = RedisDefaultModules> = IRedisClientPoolType<T>;
94
+ export type RedisClientPoolInstance = IRedisClientPoolType<any, any, any, any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
88
95
  export type RedisHSETObject = Record<number | string, RedisCommandValue>;
89
96
  export type DbPoolCredential = RedisClientOptions & IdentifierAction;
90
97
 
91
- export type { HScanTuple, RedisCommandArgument, RedisJSON, ScanOptions };
98
+ export type { CommandOptions, RedisArgument, RedisJSON, ScanOptions };