@pi-r/redis 0.11.2 → 0.12.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 +65 -55
- package/client/pool.js +3 -0
- package/package.json +4 -4
- package/types/index.d.ts +3 -3
package/client/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const DbPool = require("@pi-r/redis/client/pool");
|
|
3
|
+
const redis = require('redis');
|
|
4
|
+
const { randomUUID } = require('node:crypto');
|
|
5
|
+
const Db = require('@e-mc/db');
|
|
6
|
+
const { DB_TYPE, asFunction, coerceObject, errorMessage, isArray, isError, isObject, isPlainObject, isString } = require('@e-mc/types');
|
|
7
|
+
const { parseConnectionString, parseServerAuth } = require('@e-mc/db/util');
|
|
8
|
+
const DbPool = require('@pi-r/redis/client/pool');
|
|
10
9
|
const POOL_STATE = {};
|
|
11
10
|
async function doScan(client, key, index, cursor = [], iterations = [], options) {
|
|
12
11
|
const target = (Array.isArray(cursor) ? cursor : [cursor]).map(item => Buffer.isBuffer(item) ? item : item.toString());
|
|
@@ -21,17 +20,17 @@ async function doScan(client, key, index, cursor = [], iterations = [], options)
|
|
|
21
20
|
} while (+current > 0 && --length > 0);
|
|
22
21
|
return result;
|
|
23
22
|
}
|
|
24
|
-
const convertFloat = (value) => typeof value === 'number' ||
|
|
23
|
+
const convertFloat = (value) => typeof value === 'number' || isString(value) && !isNaN(value = +value) ? value : undefined;
|
|
25
24
|
async function setCredential(item) {
|
|
26
25
|
const credential = this.getCredential(item);
|
|
27
26
|
const options = item.options ||= {};
|
|
28
|
-
const client =
|
|
27
|
+
const client = isPlainObject(options.client) ? options.client : options.client = {};
|
|
29
28
|
let { uri, username, password, database = 0 } = item;
|
|
30
29
|
if (database > 0) {
|
|
31
30
|
client.database = database;
|
|
32
31
|
}
|
|
33
32
|
if (credential) {
|
|
34
|
-
const auth =
|
|
33
|
+
const auth = parseServerAuth(credential, 6379);
|
|
35
34
|
if (auth.database && (database = +auth.database) > 0) {
|
|
36
35
|
client.database ||= database;
|
|
37
36
|
}
|
|
@@ -43,7 +42,7 @@ async function setCredential(item) {
|
|
|
43
42
|
delete item.credential;
|
|
44
43
|
}
|
|
45
44
|
if (!uri) {
|
|
46
|
-
throw
|
|
45
|
+
throw errorMessage("redis", "Invalid credentials", 'url');
|
|
47
46
|
}
|
|
48
47
|
if (client.socket?.tls) {
|
|
49
48
|
this.readTLSConfig(client.socket);
|
|
@@ -62,8 +61,8 @@ async function setCredential(item) {
|
|
|
62
61
|
let pool;
|
|
63
62
|
username = undefined;
|
|
64
63
|
password = undefined;
|
|
65
|
-
if (
|
|
66
|
-
username = client.username ||
|
|
64
|
+
if (isString(usePool)) {
|
|
65
|
+
username = client.username || parseConnectionString(uri)?.username;
|
|
67
66
|
if (username) {
|
|
68
67
|
[password, pool] = DbPool.validateKey(POOL_STATE, username, usePool);
|
|
69
68
|
if (pool) {
|
|
@@ -79,13 +78,16 @@ async function setCredential(item) {
|
|
|
79
78
|
const config = this.getPoolConfig("redis", password);
|
|
80
79
|
const poolOptions = {};
|
|
81
80
|
if (config) {
|
|
82
|
-
const { min, max, timeout } = config;
|
|
81
|
+
const { min, max, idle, timeout } = config;
|
|
83
82
|
if (min >= 0) {
|
|
84
83
|
poolOptions.minimum ??= min;
|
|
85
84
|
}
|
|
86
85
|
if (max > 0) {
|
|
87
86
|
poolOptions.maximum ??= max;
|
|
88
87
|
}
|
|
88
|
+
if (idle > 0) {
|
|
89
|
+
poolOptions.cleanupDelay ??= idle;
|
|
90
|
+
}
|
|
89
91
|
if (timeout > 0) {
|
|
90
92
|
poolOptions.acquireTimeout ??= timeout;
|
|
91
93
|
}
|
|
@@ -117,7 +119,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
117
119
|
return [];
|
|
118
120
|
}
|
|
119
121
|
let parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss;
|
|
120
|
-
if (
|
|
122
|
+
if (isPlainObject(options)) {
|
|
121
123
|
({ parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss } = options);
|
|
122
124
|
}
|
|
123
125
|
else {
|
|
@@ -175,8 +177,8 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
175
177
|
for (let i = 0; i < length; ++i) {
|
|
176
178
|
const item = batch[i];
|
|
177
179
|
const { source, key, format = 'HASH', search, aggregate, streams, options: clientOptions = {}, ignoreCache } = item;
|
|
178
|
-
let credential =
|
|
179
|
-
if (!credential && !
|
|
180
|
+
let credential = redisCredential || onceCredential, error;
|
|
181
|
+
if (!credential && !isPlainObject(credential = clientOptions.client) && (error = errorMessage(source, "Invalid credentials")) || !(isString(key) || isArray(key) || isPlainObject(search) && (isPlainObject(search.schema) || isString(search.query)) || isPlainObject(aggregate) && (isPlainObject(aggregate.schema) || isString(aggregate.query))) && (error = errorMessage(source, "Missing database query", item.uri))) {
|
|
180
182
|
if (this.handleFail(error, item, { errorQuery })) {
|
|
181
183
|
if (!parallel) {
|
|
182
184
|
tasks.length = 0;
|
|
@@ -194,14 +196,14 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
194
196
|
}
|
|
195
197
|
item.transactionState = 1;
|
|
196
198
|
const uuidKey = credential.uuidKey ||= (onceCredential ? batch[0] : item).credential?.uuidKey;
|
|
197
|
-
const targetObject = typeof checkObject === 'string' ? this.hasCoerce("redis", 'options', uuidKey)
|
|
199
|
+
const targetObject = typeof checkObject === 'string' ? this.hasCoerce("redis", 'options', uuidKey) ? asFunction(checkObject) : null : checkObject;
|
|
198
200
|
const cacheValue = ignoreCache === undefined ? sessionKey : Array.isArray(ignoreCache) ? { sessionKey, exclusiveOf: ignoreCache } : { sessionKey, renewCache: ignoreCache === 0 };
|
|
199
201
|
let queryString = '', rows;
|
|
200
202
|
if (caching && ignoreCache !== true) {
|
|
201
|
-
if (
|
|
203
|
+
if (isObject(search)) {
|
|
202
204
|
queryString = Db.asString(search, true);
|
|
203
205
|
}
|
|
204
|
-
else if (
|
|
206
|
+
else if (isObject(aggregate)) {
|
|
205
207
|
queryString = Db.asString(aggregate, true);
|
|
206
208
|
}
|
|
207
209
|
else if (streams) {
|
|
@@ -251,18 +253,18 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
251
253
|
const b = (arg) => typeof arg === 'number' || Buffer.isBuffer(arg) ? arg.toString() : arg;
|
|
252
254
|
if (item.update) {
|
|
253
255
|
commandType = this.commandType.UPDATE;
|
|
254
|
-
const values = (
|
|
256
|
+
const values = (Array.isArray(item.update) ? item.update : [item.update]).map(async (target) => {
|
|
255
257
|
return new Promise(async (success, failed) => {
|
|
256
258
|
let { key: k, value: v, format: f, NX, XX, options: setOptions = {} } = target;
|
|
257
|
-
f = (
|
|
259
|
+
f = (isString(f) ? f.toUpperCase() : 'HASH');
|
|
258
260
|
if (v === undefined && f !== 'JSON') {
|
|
259
261
|
success(target);
|
|
260
262
|
return;
|
|
261
263
|
}
|
|
262
264
|
const has = (n) => typeof n === 'number' && n > 0;
|
|
263
|
-
const failKey = () => failed(
|
|
264
|
-
const failValue = () => failed(
|
|
265
|
-
if (
|
|
265
|
+
const failKey = () => failed(errorMessage(f, "Invalid key", Db.asString(k) || "Unknown"));
|
|
266
|
+
const failValue = () => failed(errorMessage(f, "Invalid value", Db.asString(v) || "Unknown"));
|
|
267
|
+
if (isPlainObject(setOptions.command)) {
|
|
266
268
|
client = client.withCommandOptions(setOptions.command);
|
|
267
269
|
}
|
|
268
270
|
let pending, codeMin = 0, EX, PX, EXAT, PXAT, KEEPTTL;
|
|
@@ -271,16 +273,16 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
271
273
|
failKey();
|
|
272
274
|
return;
|
|
273
275
|
}
|
|
274
|
-
if (this.hasCoerce("redis", 'options', uuidKey) &&
|
|
275
|
-
({ outV: v } =
|
|
276
|
+
if (this.hasCoerce("redis", 'options', uuidKey) && isString(v) && v.startsWith('new')) {
|
|
277
|
+
({ outV: v } = coerceObject({ outV: v }));
|
|
276
278
|
}
|
|
277
279
|
const { path = '$', command: cmd, start, stop, index } = target;
|
|
278
|
-
const name = (
|
|
280
|
+
const name = (isString(cmd) ? cmd.toUpperCase() : 'SET');
|
|
279
281
|
switch (name) {
|
|
280
282
|
case 'ARRAPPEND':
|
|
281
283
|
case 'ARRINSERT': {
|
|
282
284
|
let v1, v2;
|
|
283
|
-
if (
|
|
285
|
+
if (isArray(v)) {
|
|
284
286
|
v1 = v[0];
|
|
285
287
|
v2 = v.slice(1);
|
|
286
288
|
}
|
|
@@ -343,7 +345,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
343
345
|
break;
|
|
344
346
|
}
|
|
345
347
|
case 'STRAPPEND':
|
|
346
|
-
if (
|
|
348
|
+
if (isString(v)) {
|
|
347
349
|
pending = client.json.strAppend(k, v, { path });
|
|
348
350
|
}
|
|
349
351
|
break;
|
|
@@ -389,7 +391,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
389
391
|
codeMin = NaN;
|
|
390
392
|
pending = client.set(k, v, flags);
|
|
391
393
|
}
|
|
392
|
-
else if (
|
|
394
|
+
else if (isObject(v)) {
|
|
393
395
|
let valid = true;
|
|
394
396
|
if (NX || XX) {
|
|
395
397
|
valid = await client.exists(k) === 1;
|
|
@@ -430,7 +432,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
430
432
|
}
|
|
431
433
|
if (pending) {
|
|
432
434
|
pending.then(async (code) => {
|
|
433
|
-
if (code == null || code !== -Infinity &&
|
|
435
|
+
if (code == null || code !== -Infinity && isString(code) && code !== 'OK' || typeof code === 'number' && code <= codeMin || Array.isArray(code) && code.every(resp => resp === null || typeof resp === 'number' && resp <= codeMin)) {
|
|
434
436
|
failValue();
|
|
435
437
|
return;
|
|
436
438
|
}
|
|
@@ -468,7 +470,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
468
470
|
success(target);
|
|
469
471
|
}
|
|
470
472
|
else {
|
|
471
|
-
failed(
|
|
473
|
+
failed(errorMessage(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
|
|
472
474
|
}
|
|
473
475
|
})
|
|
474
476
|
.catch(failed);
|
|
@@ -499,39 +501,49 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
499
501
|
}
|
|
500
502
|
});
|
|
501
503
|
}
|
|
502
|
-
const commandOptions = credential.commandOptions
|
|
503
|
-
if (
|
|
504
|
+
const commandOptions = credential.commandOptions;
|
|
505
|
+
if (isPlainObject(commandOptions)) {
|
|
504
506
|
commandOptions.abortSignal ||= this.signal;
|
|
505
507
|
client = client.withCommandOptions(commandOptions);
|
|
506
508
|
}
|
|
507
509
|
commandType = this.commandType.SELECT;
|
|
508
|
-
const target =
|
|
510
|
+
const target = isObject(search) ? search : isObject(aggregate) ? aggregate : null;
|
|
509
511
|
if (target) {
|
|
510
512
|
const { query = '', schema, index } = target;
|
|
513
|
+
const ftType = () => target === search ? clientOptions.hybrid ? 'FT.HYBRID' : 'FT.SEARCH' : 'FT.AGGREGATE';
|
|
511
514
|
let idx = '';
|
|
512
|
-
if (
|
|
513
|
-
idx = index ||
|
|
515
|
+
if (isObject(schema)) {
|
|
516
|
+
idx = index || randomUUID();
|
|
514
517
|
await client.ft.create(idx, schema, target.options);
|
|
515
518
|
}
|
|
516
519
|
try {
|
|
517
520
|
if (target === search) {
|
|
518
|
-
|
|
519
|
-
|
|
521
|
+
if (clientOptions.hybrid) {
|
|
522
|
+
const reply = await client.ft.hybrid(index || idx, clientOptions.hybrid);
|
|
523
|
+
rows = reply.results;
|
|
524
|
+
if (isArray(reply.warnings)) {
|
|
525
|
+
reply.warnings.forEach(msg => this.addLog(3, msg, "redis", 'FT.HYBRID'));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
const reply = await client.ft.search(index || idx, query, clientOptions.search);
|
|
530
|
+
rows = reply.documents.map(doc => (doc.value.__id__ = doc.id) && doc.value);
|
|
531
|
+
}
|
|
520
532
|
}
|
|
521
533
|
else {
|
|
522
534
|
({ results: rows } = await client.ft.aggregate(index || idx, query, clientOptions.aggregate));
|
|
523
535
|
}
|
|
524
536
|
}
|
|
525
537
|
catch (err) {
|
|
526
|
-
this.addLog(3, err,
|
|
527
|
-
if (err
|
|
538
|
+
this.addLog(3, err, "redis", ftType());
|
|
539
|
+
if (isError(err)) {
|
|
528
540
|
throw err;
|
|
529
541
|
}
|
|
530
542
|
}
|
|
531
543
|
finally {
|
|
532
544
|
if (idx && idx !== index) {
|
|
533
545
|
client.ft.dropIndex(idx).catch((err) => {
|
|
534
|
-
this.writeFail([`Unable to drop ${
|
|
546
|
+
this.writeFail([`Unable to drop ${ftType()} index`, idx], err, { type: 65536, fatal: false });
|
|
535
547
|
});
|
|
536
548
|
}
|
|
537
549
|
}
|
|
@@ -599,22 +611,20 @@ async function executeBatchQuery(batch, options = '', outResult) {
|
|
|
599
611
|
rows = (targetObject ? targetObject(item, data) : data);
|
|
600
612
|
}
|
|
601
613
|
if (rows == null) {
|
|
602
|
-
throw
|
|
614
|
+
throw errorMessage(source, "Missing database query");
|
|
603
615
|
}
|
|
604
616
|
this.add(item, 4);
|
|
605
617
|
resolve(this.setQueryResult(source, DbPool.sanitize(redisCredential || credential), queryString, rows, cacheValue));
|
|
606
618
|
}
|
|
607
619
|
catch (err) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
await pool.detach(true);
|
|
615
|
-
}
|
|
616
|
-
break;
|
|
620
|
+
switch (isError(err) && err.message) {
|
|
621
|
+
case "The client is closed":
|
|
622
|
+
case "Disconnects client": {
|
|
623
|
+
const pool = item.usePool && DbPool.findKey(POOL_STATE, item.usePool, DbPool.asString(credential));
|
|
624
|
+
if (pool) {
|
|
625
|
+
await pool.detach(true);
|
|
617
626
|
}
|
|
627
|
+
break;
|
|
618
628
|
}
|
|
619
629
|
}
|
|
620
630
|
if (this.handleFail(err, item, { errorQuery, commandType })) {
|
|
@@ -651,7 +661,7 @@ async function checkTimeout(value, limit = 0) {
|
|
|
651
661
|
return DbPool.checkTimeout(POOL_STATE, value, limit);
|
|
652
662
|
}
|
|
653
663
|
const DB_SOURCE_CLIENT = true;
|
|
654
|
-
const DB_SOURCE_TYPE =
|
|
664
|
+
const DB_SOURCE_TYPE = DB_TYPE.NOSQL | DB_TYPE.KEYVALUE;
|
|
655
665
|
|
|
656
666
|
exports.DB_SOURCE_CLIENT = DB_SOURCE_CLIENT;
|
|
657
667
|
exports.DB_SOURCE_TYPE = DB_SOURCE_TYPE;
|
package/client/pool.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-r/redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Redis client driver for E-mc.",
|
|
5
5
|
"main": "client/index.js",
|
|
6
6
|
"types": "client/index.d.ts",
|
|
@@ -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.
|
|
24
|
-
"@e-mc/types": "^0.
|
|
25
|
-
"redis": "^5.
|
|
23
|
+
"@e-mc/db": "^0.14.0",
|
|
24
|
+
"@e-mc/types": "^0.14.0",
|
|
25
|
+
"redis": "^5.12.1"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { CommandOptions } from '@redis/client/dist/lib/client/commands-queu
|
|
|
11
11
|
import type { ScanOptions } from '@redis/client/dist/lib/commands/SCAN';
|
|
12
12
|
import type { CreateOptions } from '@redis/search/dist/lib/commands/CREATE';
|
|
13
13
|
import type { FtAggregateOptions } from '@redis/search/dist/lib/commands/AGGREGATE';
|
|
14
|
+
import type { FtHybridOptions } from '@redis/search/dist/lib/commands/HYBRID';
|
|
14
15
|
import type { FtSearchOptions } from '@redis/search/dist/lib/commands/SEARCH';
|
|
15
16
|
import type { JsonGetOptions } from '@redis/json/dist/lib/commands/GET';
|
|
16
17
|
import type { XReadOptions, XReadStreams } from '@redis/client/dist/lib/commands/XREAD';
|
|
@@ -29,10 +30,9 @@ export interface RedisDataSource extends DbDataSource<string, PlainObject, Redis
|
|
|
29
30
|
iterations?: number | number[];
|
|
30
31
|
options?: {
|
|
31
32
|
client?: RedisClientOptions;
|
|
32
|
-
/** @deprecated client.commandOptions */
|
|
33
|
-
command?: CommandOptions;
|
|
34
33
|
get?: JsonGetOptions;
|
|
35
34
|
search?: FtSearchOptions;
|
|
35
|
+
hybrid?: FtHybridOptions;
|
|
36
36
|
aggregate?: FtAggregateOptions;
|
|
37
37
|
xread?: XReadOptions;
|
|
38
38
|
scan?: ScanOptions;
|
|
@@ -91,7 +91,7 @@ export type RedisCommandJSON = "ARRAPPEND" | "ARRINDEX" | "ARRINSERT" | "ARRPOP"
|
|
|
91
91
|
export type RedisExpireCondition = "NX" | "XX" | "GT" | "LT";
|
|
92
92
|
export type RedisCommandValue = RedisArgument | number;
|
|
93
93
|
export type RedisClientPoolType<T extends RedisModules = RedisDefaultModules> = IRedisClientPoolType<T>;
|
|
94
|
-
export type RedisClientPoolInstance = IRedisClientPoolType<any, any, any, any, any>;
|
|
94
|
+
export type RedisClientPoolInstance = IRedisClientPoolType<any, any, any, any, any>;
|
|
95
95
|
export type RedisHSETObject = Record<number | string, RedisCommandValue>;
|
|
96
96
|
export type DbPoolCredential = RedisClientOptions & IdentifierAction;
|
|
97
97
|
|