@pi-r/redis 0.10.3 → 0.11.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/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,76 @@ 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
263
  const failKey = () => {
282
- failed((0, types_1.errorMessage)(f, "Invalid key", Db.asString(k) || "Unknown"));
264
+ failed(types.errorMessage(f, "Invalid key", Db.asString(k) || "Unknown"));
283
265
  };
284
266
  const failValue = () => {
285
- failed((0, types_1.errorMessage)(f, "Invalid value", Db.asString(v) || "Unknown"));
267
+ failed(types.errorMessage(f, "Invalid value", Db.asString(v) || "Unknown"));
286
268
  };
287
- const commandSet = (0, types_1.isPlainObject)(setOptions.command) && redis.commandOptions(setOptions.command);
269
+ if (types.isPlainObject(setOptions.command)) {
270
+ client = client.withCommandOptions(setOptions.command);
271
+ }
288
272
  let pending, codeMin = 0, EX, PX, EXAT, PXAT, KEEPTTL;
289
273
  if (f === 'JSON') {
290
274
  if (typeof k !== 'string') {
291
275
  failKey();
292
276
  return;
293
277
  }
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];
278
+ if (this.hasCoerce("redis", 'options', uuidKey) && types.isString(v) && v.startsWith('new')) {
279
+ ({ outV: v } = types.coerceObject({ outV: v }));
301
280
  }
281
+ const { path = '$', command: cmd, start, stop, index } = target;
282
+ const name = (types.isString(cmd) ? cmd.toUpperCase() : 'SET');
302
283
  switch (name) {
303
284
  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]));
285
+ case 'ARRINSERT': {
286
+ let v1, v2;
287
+ if (types.isArray(v)) {
288
+ v1 = v[0];
289
+ v2 = v.slice(1);
290
+ }
291
+ else {
292
+ v1 = v;
293
+ v2 = [];
294
+ }
295
+ if (name === 'ARRAPPEND') {
296
+ pending = client.json.arrAppend(k, path, v1, ...v2);
297
+ }
298
+ else if (typeof index === 'number') {
299
+ pending = client.json.arrInsert(k, path, index, v1, ...v2);
300
+ }
305
301
  break;
306
- case 'ARRINDEX':
302
+ }
303
+ case 'ARRINDEX': {
304
+ const s1 = convertFloat(start);
307
305
  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
- }
306
+ pending = client.json.arrIndex(k, path, v, s1 !== undefined ? { range: { start: s1, stop: convertFloat(stop) } } : undefined);
314
307
  break;
308
+ }
315
309
  case 'ARRPOP':
316
310
  codeMin = -Infinity;
317
- pending = commandSet ? client.json.arrPop(commandSet, k, path, index) : client.json.arrPop(k, path, index);
311
+ pending = client.json.arrPop(k, { path, index: convertFloat(index) });
318
312
  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);
313
+ case 'ARRTRIM': {
314
+ const s1 = convertFloat(start);
315
+ const s2 = convertFloat(stop);
316
+ if (s1 !== undefined && s2 !== undefined) {
317
+ pending = client.json.arrTrim(k, path, s1, s2);
322
318
  }
323
319
  break;
320
+ }
324
321
  case 'DEL':
325
322
  case 'FORGET':
326
- pending = commandSet ? client.json[name === 'DEL' ? 'del' : 'forget'](commandSet, k, path) : client.json[name === 'DEL' ? 'del' : 'forget'](k, path);
323
+ pending = client.json[name === 'DEL' ? 'del' : 'forget'](k, { path });
327
324
  break;
328
325
  case 'MERGE':
329
- pending = commandSet ? client.json.merge(commandSet, k, path, v) : client.json.merge(k, path, v);
326
+ pending = client.json.merge(k, path, v);
330
327
  break;
331
328
  case 'MSET': {
332
329
  const data = (Array.isArray(v) ? v : [v])
@@ -337,24 +334,26 @@ async function executeBatchQuery(batch, options = '', outResult) {
337
334
  return m;
338
335
  });
339
336
  if (data.length > 0) {
340
- pending = commandSet ? client.json.mSet(commandSet, data) : client.json.mSet(data);
337
+ pending = client.json.mSet(data);
341
338
  }
342
339
  break;
343
340
  }
344
341
  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);
342
+ case 'NUMMULTBY': {
343
+ const n = convertFloat(v);
344
+ if (n !== undefined) {
345
+ pending = client.json[name === 'NUMINCRBY' ? 'numIncrBy' : 'numMultBy'](k, path, n);
348
346
  }
349
347
  break;
348
+ }
350
349
  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);
350
+ if (types.isString(v)) {
351
+ pending = client.json.strAppend(k, v, { path });
353
352
  }
354
353
  break;
355
354
  default: {
356
355
  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);
356
+ pending = client.json.set(k, path, v, flags);
358
357
  break;
359
358
  }
360
359
  }
@@ -366,35 +365,35 @@ async function executeBatchQuery(batch, options = '', outResult) {
366
365
  if (a(v)) {
367
366
  const flags = setOptions.set || {};
368
367
  if (NX) {
369
- flags.NX = true;
368
+ flags.condition = 'NX';
370
369
  }
371
370
  if (XX) {
372
- flags.XX = true;
371
+ flags.condition = 'XX';
373
372
  }
374
373
  if (has(EX)) {
375
- flags.EX = EX;
374
+ flags.expiration = { type: 'EX', value: EX };
376
375
  EX = 0;
377
376
  }
378
377
  if (has(PX)) {
379
- flags.PX = PX;
378
+ flags.expiration = { type: 'PX', value: PX };
380
379
  PX = 0;
381
380
  }
382
381
  if (has(EXAT)) {
383
- flags.EXAT = EXAT;
382
+ flags.expiration = { type: 'EXAT', value: EXAT };
384
383
  EXAT = 0;
385
384
  }
386
385
  if (has(PXAT)) {
387
- flags.PXAT = PXAT;
386
+ flags.expiration = { type: 'PXAT', value: PXAT };
388
387
  PXAT = 0;
389
388
  }
390
389
  if (KEEPTTL) {
391
- flags.KEEPTTL = true;
390
+ flags.expiration = { type: 'KEEPTTL' };
392
391
  KEEPTTL = false;
393
392
  }
394
393
  codeMin = NaN;
395
- pending = commandSet ? client.set(commandSet, k, v, flags) : client.set(k, v, flags);
394
+ pending = client.set(k, v, flags);
396
395
  }
397
- else if ((0, types_1.isObject)(v)) {
396
+ else if (types.isObject(v)) {
398
397
  let valid = true;
399
398
  if (NX || XX) {
400
399
  valid = await client.exists(k) === 1;
@@ -406,26 +405,26 @@ async function executeBatchQuery(batch, options = '', outResult) {
406
405
  success(target);
407
406
  return;
408
407
  }
409
- pending = commandSet ? client.hSet(commandSet, k, v) : client.hSet(k, v);
408
+ pending = client.hSet(k, v);
410
409
  }
411
410
  }
412
411
  else if (NX) {
413
412
  if (a(field) && a(v)) {
414
413
  codeMin = -1;
415
- pending = commandSet ? client.hSetNX(commandSet, k, b(field), b(v)) : client.hSetNX(k, b(field), b(v));
414
+ pending = client.hSetNX(k, b(field), b(v));
416
415
  }
417
416
  }
418
417
  else {
419
- let valid = true;
420
- if (XX) {
421
- valid = a(k) && a(field) && await client.hExists(b(k), b(field));
418
+ let valid = 1;
419
+ if (XX && a(k) && a(field)) {
420
+ valid = await client.hExists(b(k), b(field));
422
421
  }
423
422
  if (a(field) && a(v)) {
424
- if (!valid) {
423
+ if (valid === 0) {
425
424
  success(target);
426
425
  return;
427
426
  }
428
- pending = commandSet ? client.hSet(commandSet, k, field, v) : client.hSet(k, field, v);
427
+ pending = client.hSet(k, field, v);
429
428
  }
430
429
  }
431
430
  }
@@ -435,7 +434,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
435
434
  }
436
435
  if (pending) {
437
436
  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)) {
437
+ if (code === undefined || 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
438
  failValue();
440
439
  return;
441
440
  }
@@ -473,7 +472,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
473
472
  success(target);
474
473
  }
475
474
  else {
476
- failed((0, types_1.errorMessage)(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
475
+ failed(types.errorMessage(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
477
476
  }
478
477
  })
479
478
  .catch(failed);
@@ -504,26 +503,31 @@ async function executeBatchQuery(batch, options = '', outResult) {
504
503
  }
505
504
  });
506
505
  }
506
+ const commandOptions = credential.commandOptions || clientOptions.command;
507
+ if (types.isPlainObject(commandOptions)) {
508
+ commandOptions.abortSignal ||= this.signal;
509
+ client = client.withCommandOptions(commandOptions);
510
+ }
507
511
  commandType = this.commandType.SELECT;
508
- const target = (0, types_1.isObject)(search) ? search : (0, types_1.isObject)(aggregate) ? aggregate : null;
512
+ const target = types.isObject(search) ? search : types.isObject(aggregate) ? aggregate : null;
509
513
  if (target) {
510
514
  const { query = '', schema, index } = target;
511
515
  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));
516
+ if (types.isObject(schema)) {
517
+ idx = index || node_crypto.randomUUID();
518
+ await client.ft.create(idx, schema, target.options);
515
519
  }
516
520
  try {
517
521
  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));
522
+ const reply = await client.ft.search(index || idx, query, clientOptions.search);
519
523
  rows = reply.documents.map(doc => (doc.value.__id__ = doc.id) && doc.value);
520
524
  }
521
525
  else {
522
- ({ results: rows } = await (command ? client.ft.aggregate(command, index || idx, query, clientOptions.aggregate) : client.ft.aggregate(index || idx, query, clientOptions.aggregate)));
526
+ ({ results: rows } = await client.ft.aggregate(index || idx, query, clientOptions.aggregate));
523
527
  }
524
528
  }
525
529
  catch (err) {
526
- this.addLog(this.statusType.WARN, err, { source: target === search ? 'FT.SEARCH' : 'FT.AGGREGATE' });
530
+ this.addLog(3, err, { source: target === search ? 'FT.SEARCH' : 'FT.AGGREGATE' });
527
531
  if (err instanceof Error) {
528
532
  throw err;
529
533
  }
@@ -536,50 +540,62 @@ async function executeBatchQuery(batch, options = '', outResult) {
536
540
  }
537
541
  }
538
542
  }
543
+ else if (streams) {
544
+ const data = await client.xRead(streams, clientOptions.xread);
545
+ if (data) {
546
+ rows = data;
547
+ }
548
+ }
539
549
  else if (key) {
540
550
  let data;
541
551
  if (Array.isArray(key)) {
542
552
  switch (format.toUpperCase()) {
543
553
  case 'HKEYS':
544
- data = await Promise.all(key.map(async (k) => client.hKeys(...command ? [command, k] : [k])));
554
+ data = await Promise.all(key.map(async (k) => client.hKeys(k)));
545
555
  break;
546
556
  case 'HVALS':
547
- data = await Promise.all(key.map(async (k) => client.hVals(...command ? [command, k] : [k])));
557
+ data = await Promise.all(key.map(async (k) => client.hVals(k)));
548
558
  break;
549
559
  case 'HSCAN':
550
560
  data = await Promise.all(key.map(async (k, index) => doScan(client, k, index, item.cursor, item.iterations, clientOptions.scan)));
551
561
  break;
562
+ case 'SMEMBERS':
563
+ data = await Promise.all(key.map(async (k) => client.sMembers(k)));
564
+ break;
552
565
  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();
566
+ data = (await client.json.mGet(key.map(c => b(c)), item.path || '$')).flat();
554
567
  break;
555
568
  default:
556
- data = await client.mGet(...command ? [command, key] : [key]);
569
+ data = await client.mGet(key);
557
570
  break;
558
571
  }
559
572
  }
560
573
  else {
561
574
  switch (format.toUpperCase()) {
562
575
  case 'HKEYS':
563
- data = await client.hKeys(...command ? [command, key] : [key]);
576
+ data = await client.hKeys(key);
564
577
  break;
565
578
  case 'HVALS':
566
- data = await client.hVals(...command ? [command, key] : [key]);
579
+ data = await client.hVals(key);
567
580
  break;
568
581
  case 'HSCAN':
569
582
  data = await doScan(client, key, 0, item.cursor, item.iterations, clientOptions.scan);
570
583
  break;
584
+ case 'SMEMBERS':
585
+ data = await client.sMembers(key);
586
+ break;
571
587
  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)]));
588
+ data = await client.json.get(b(key), clientOptions.get);
573
589
  break;
574
590
  default:
575
591
  if (Array.isArray(item.field)) {
576
- data = await client.hmGet(...command ? [command, key, item.field] : [key, item.field]);
592
+ data = await client.hmGet(key, item.field);
577
593
  }
578
594
  else if (item.field) {
579
- data = await client.hGet(...command ? [command, key, item.field] : [key, item.field]);
595
+ data = await client.hGet(key, item.field);
580
596
  }
581
597
  else {
582
- data = await client.hGetAll(...command ? [command, key] : [key]);
598
+ data = await client.hGetAll(key);
583
599
  }
584
600
  break;
585
601
  }
@@ -587,7 +603,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
587
603
  rows = (targetObject ? targetObject(item, data) : data);
588
604
  }
589
605
  if (rows === undefined) {
590
- throw (0, types_1.errorMessage)(source, "Missing database query");
606
+ throw types.errorMessage(source, "Missing database query");
591
607
  }
592
608
  this.add(item, 4);
593
609
  resolve(this.setQueryResult(source, DbPool.sanitize(redisCredential || credential), queryString, rows, cacheValue));
@@ -623,11 +639,14 @@ async function executeBatchQuery(batch, options = '', outResult) {
623
639
  }
624
640
  }
625
641
  }
626
- return this.processRows(batch, tasks, clients.length === 0 ? parallel : {
642
+ return this.processRows(batch, tasks, {
627
643
  parallel,
628
644
  disconnect() {
629
645
  for (const item of clients) {
630
- void item.disconnect();
646
+ item.destroy();
647
+ }
648
+ for (const item of pools) {
649
+ void item.close();
631
650
  }
632
651
  }
633
652
  }, outResult);
@@ -635,5 +654,12 @@ async function executeBatchQuery(batch, options = '', outResult) {
635
654
  async function checkTimeout(value, limit = 0) {
636
655
  return DbPool.checkTimeout(POOL_STATE, value, limit);
637
656
  }
638
- exports.DB_SOURCE_CLIENT = true;
639
- exports.DB_SOURCE_TYPE = types_1.DB_TYPE.NOSQL | types_1.DB_TYPE.KEYVALUE;
657
+ const DB_SOURCE_CLIENT = true;
658
+ const DB_SOURCE_TYPE = types.DB_TYPE.NOSQL | types.DB_TYPE.KEYVALUE;
659
+
660
+ exports.DB_SOURCE_CLIENT = DB_SOURCE_CLIENT;
661
+ exports.DB_SOURCE_TYPE = DB_SOURCE_TYPE;
662
+ exports.checkTimeout = checkTimeout;
663
+ exports.executeBatchQuery = executeBatchQuery;
664
+ exports.executeQuery = executeQuery;
665
+ 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.0",
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.0",
24
+ "@e-mc/types": "^0.13.0",
25
+ "redis": "^5.8.3"
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 };