@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 +1 -1
- package/client/index.js +166 -144
- package/client/pool.d.ts +2 -4
- package/client/pool.js +6 -7
- package/package.json +5 -5
- package/types/index.d.ts +32 -25
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 = [
|
|
15
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
87
|
-
username = client.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 =
|
|
80
|
+
const poolOptions = {};
|
|
102
81
|
if (config) {
|
|
103
82
|
const { min, max, timeout } = config;
|
|
104
83
|
if (min >= 0) {
|
|
105
|
-
poolOptions.
|
|
84
|
+
poolOptions.minimum ??= min;
|
|
106
85
|
}
|
|
107
86
|
if (max > 0) {
|
|
108
|
-
poolOptions.
|
|
87
|
+
poolOptions.maximum ??= max;
|
|
109
88
|
}
|
|
110
89
|
if (timeout > 0) {
|
|
111
|
-
poolOptions.
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 && !
|
|
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) &&
|
|
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 (
|
|
201
|
+
if (types.isObject(search)) {
|
|
222
202
|
queryString = Db.asString(search, true);
|
|
223
203
|
}
|
|
224
|
-
else if (
|
|
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
|
-
|
|
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 = (
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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) &&
|
|
295
|
-
({ 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
|
-
|
|
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
|
-
|
|
298
|
+
}
|
|
299
|
+
case 'ARRINDEX': {
|
|
300
|
+
const s1 = convertFloat(start);
|
|
307
301
|
codeMin = -1;
|
|
308
|
-
pending =
|
|
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 =
|
|
307
|
+
pending = client.json.arrPop(k, { path, index: convertFloat(index) });
|
|
318
308
|
break;
|
|
319
|
-
case 'ARRTRIM':
|
|
320
|
-
|
|
321
|
-
|
|
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 =
|
|
319
|
+
pending = client.json[name === 'DEL' ? 'del' : 'forget'](k, { path });
|
|
327
320
|
break;
|
|
328
321
|
case 'MERGE':
|
|
329
|
-
pending =
|
|
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 =
|
|
333
|
+
pending = client.json.mSet(data);
|
|
341
334
|
}
|
|
342
335
|
break;
|
|
343
336
|
}
|
|
344
337
|
case 'NUMINCRBY':
|
|
345
|
-
case 'NUMMULTBY':
|
|
346
|
-
|
|
347
|
-
|
|
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 (
|
|
352
|
-
pending =
|
|
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 =
|
|
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
|
|
360
|
+
if (field == null) {
|
|
366
361
|
if (a(v)) {
|
|
367
362
|
const flags = setOptions.set || {};
|
|
368
363
|
if (NX) {
|
|
369
|
-
flags.
|
|
364
|
+
flags.condition = 'NX';
|
|
370
365
|
}
|
|
371
366
|
if (XX) {
|
|
372
|
-
flags.
|
|
367
|
+
flags.condition = 'XX';
|
|
373
368
|
}
|
|
374
369
|
if (has(EX)) {
|
|
375
|
-
flags.
|
|
370
|
+
flags.expiration = { type: 'EX', value: EX };
|
|
376
371
|
EX = 0;
|
|
377
372
|
}
|
|
378
373
|
if (has(PX)) {
|
|
379
|
-
flags.
|
|
374
|
+
flags.expiration = { type: 'PX', value: PX };
|
|
380
375
|
PX = 0;
|
|
381
376
|
}
|
|
382
377
|
if (has(EXAT)) {
|
|
383
|
-
flags.
|
|
378
|
+
flags.expiration = { type: 'EXAT', value: EXAT };
|
|
384
379
|
EXAT = 0;
|
|
385
380
|
}
|
|
386
381
|
if (has(PXAT)) {
|
|
387
|
-
flags.
|
|
382
|
+
flags.expiration = { type: 'PXAT', value: PXAT };
|
|
388
383
|
PXAT = 0;
|
|
389
384
|
}
|
|
390
385
|
if (KEEPTTL) {
|
|
391
|
-
flags.
|
|
386
|
+
flags.expiration = { type: 'KEEPTTL' };
|
|
392
387
|
KEEPTTL = false;
|
|
393
388
|
}
|
|
394
389
|
codeMin = NaN;
|
|
395
|
-
pending =
|
|
390
|
+
pending = client.set(k, v, flags);
|
|
396
391
|
}
|
|
397
|
-
else if (
|
|
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 =
|
|
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 =
|
|
410
|
+
pending = client.hSetNX(k, b(field), b(v));
|
|
416
411
|
}
|
|
417
412
|
}
|
|
418
413
|
else {
|
|
419
|
-
let valid =
|
|
420
|
-
if (XX) {
|
|
421
|
-
valid =
|
|
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 (
|
|
419
|
+
if (valid === 0) {
|
|
425
420
|
success(target);
|
|
426
421
|
return;
|
|
427
422
|
}
|
|
428
|
-
pending =
|
|
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
|
|
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(
|
|
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 =
|
|
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 (
|
|
513
|
-
idx = index ||
|
|
514
|
-
await
|
|
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
|
|
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
|
|
522
|
+
({ results: rows } = await client.ft.aggregate(index || idx, query, clientOptions.aggregate));
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
catch (err) {
|
|
526
|
-
this.addLog(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
572
|
+
data = await client.hKeys(key);
|
|
564
573
|
break;
|
|
565
574
|
case 'HVALS':
|
|
566
|
-
data = await client.hVals(
|
|
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
|
|
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(
|
|
588
|
+
data = await client.hmGet(key, item.field);
|
|
577
589
|
}
|
|
578
590
|
else if (item.field) {
|
|
579
|
-
data = await client.hGet(
|
|
591
|
+
data = await client.hGet(key, item.field);
|
|
580
592
|
}
|
|
581
593
|
else {
|
|
582
|
-
data = await client.hGetAll(
|
|
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
|
|
590
|
-
throw
|
|
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,
|
|
638
|
+
return this.processRows(batch, tasks, {
|
|
627
639
|
parallel,
|
|
628
640
|
disconnect() {
|
|
629
641
|
for (const item of clients) {
|
|
630
|
-
|
|
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
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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
|
|
20
|
-
return { ...credential, socket: credential.socket?.tls ? { tls: true } : 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.
|
|
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.
|
|
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-
|
|
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.
|
|
24
|
-
"@e-mc/types": "^0.
|
|
25
|
-
"redis": "^
|
|
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 {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type { CommandOptions } from '@redis/client/dist/lib/
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
14
|
-
import type {
|
|
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?:
|
|
19
|
-
field?:
|
|
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
|
-
|
|
27
|
+
streams?: XReadStreams;
|
|
28
|
+
cursor?: RedisArgument | RedisArgument[] | number | number[];
|
|
25
29
|
iterations?: number | number[];
|
|
26
30
|
options?: {
|
|
27
31
|
client?: RedisClientOptions;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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?:
|
|
55
|
+
command?: CommandOptions;
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
export interface RedisSetValue extends RedisCommand<"HASH",
|
|
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?:
|
|
80
|
+
options?: T;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
export interface JsonMSetItem {
|
|
78
|
-
key:
|
|
79
|
-
path:
|
|
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 =
|
|
87
|
-
export type
|
|
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 {
|
|
98
|
+
export type { CommandOptions, RedisArgument, RedisJSON, ScanOptions };
|