@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 +1 -1
- package/client/index.js +164 -138
- 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,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 = (
|
|
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(
|
|
264
|
+
failed(types.errorMessage(f, "Invalid key", Db.asString(k) || "Unknown"));
|
|
283
265
|
};
|
|
284
266
|
const failValue = () => {
|
|
285
|
-
failed(
|
|
267
|
+
failed(types.errorMessage(f, "Invalid value", Db.asString(v) || "Unknown"));
|
|
286
268
|
};
|
|
287
|
-
|
|
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) &&
|
|
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];
|
|
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
|
-
|
|
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
|
-
|
|
302
|
+
}
|
|
303
|
+
case 'ARRINDEX': {
|
|
304
|
+
const s1 = convertFloat(start);
|
|
307
305
|
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
|
-
}
|
|
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 =
|
|
311
|
+
pending = client.json.arrPop(k, { path, index: convertFloat(index) });
|
|
318
312
|
break;
|
|
319
|
-
case 'ARRTRIM':
|
|
320
|
-
|
|
321
|
-
|
|
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 =
|
|
323
|
+
pending = client.json[name === 'DEL' ? 'del' : 'forget'](k, { path });
|
|
327
324
|
break;
|
|
328
325
|
case 'MERGE':
|
|
329
|
-
pending =
|
|
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 =
|
|
337
|
+
pending = client.json.mSet(data);
|
|
341
338
|
}
|
|
342
339
|
break;
|
|
343
340
|
}
|
|
344
341
|
case 'NUMINCRBY':
|
|
345
|
-
case 'NUMMULTBY':
|
|
346
|
-
|
|
347
|
-
|
|
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 (
|
|
352
|
-
pending =
|
|
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 =
|
|
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.
|
|
368
|
+
flags.condition = 'NX';
|
|
370
369
|
}
|
|
371
370
|
if (XX) {
|
|
372
|
-
flags.
|
|
371
|
+
flags.condition = 'XX';
|
|
373
372
|
}
|
|
374
373
|
if (has(EX)) {
|
|
375
|
-
flags.
|
|
374
|
+
flags.expiration = { type: 'EX', value: EX };
|
|
376
375
|
EX = 0;
|
|
377
376
|
}
|
|
378
377
|
if (has(PX)) {
|
|
379
|
-
flags.
|
|
378
|
+
flags.expiration = { type: 'PX', value: PX };
|
|
380
379
|
PX = 0;
|
|
381
380
|
}
|
|
382
381
|
if (has(EXAT)) {
|
|
383
|
-
flags.
|
|
382
|
+
flags.expiration = { type: 'EXAT', value: EXAT };
|
|
384
383
|
EXAT = 0;
|
|
385
384
|
}
|
|
386
385
|
if (has(PXAT)) {
|
|
387
|
-
flags.
|
|
386
|
+
flags.expiration = { type: 'PXAT', value: PXAT };
|
|
388
387
|
PXAT = 0;
|
|
389
388
|
}
|
|
390
389
|
if (KEEPTTL) {
|
|
391
|
-
flags.
|
|
390
|
+
flags.expiration = { type: 'KEEPTTL' };
|
|
392
391
|
KEEPTTL = false;
|
|
393
392
|
}
|
|
394
393
|
codeMin = NaN;
|
|
395
|
-
pending =
|
|
394
|
+
pending = client.set(k, v, flags);
|
|
396
395
|
}
|
|
397
|
-
else if (
|
|
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 =
|
|
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 =
|
|
414
|
+
pending = client.hSetNX(k, b(field), b(v));
|
|
416
415
|
}
|
|
417
416
|
}
|
|
418
417
|
else {
|
|
419
|
-
let valid =
|
|
420
|
-
if (XX) {
|
|
421
|
-
valid =
|
|
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 (
|
|
423
|
+
if (valid === 0) {
|
|
425
424
|
success(target);
|
|
426
425
|
return;
|
|
427
426
|
}
|
|
428
|
-
pending =
|
|
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 &&
|
|
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(
|
|
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 =
|
|
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 (
|
|
513
|
-
idx = index ||
|
|
514
|
-
await
|
|
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
|
|
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
|
|
526
|
+
({ results: rows } = await client.ft.aggregate(index || idx, query, clientOptions.aggregate));
|
|
523
527
|
}
|
|
524
528
|
}
|
|
525
529
|
catch (err) {
|
|
526
|
-
this.addLog(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
576
|
+
data = await client.hKeys(key);
|
|
564
577
|
break;
|
|
565
578
|
case 'HVALS':
|
|
566
|
-
data = await client.hVals(
|
|
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
|
|
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(
|
|
592
|
+
data = await client.hmGet(key, item.field);
|
|
577
593
|
}
|
|
578
594
|
else if (item.field) {
|
|
579
|
-
data = await client.hGet(
|
|
595
|
+
data = await client.hGet(key, item.field);
|
|
580
596
|
}
|
|
581
597
|
else {
|
|
582
|
-
data = await client.hGetAll(
|
|
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
|
|
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,
|
|
642
|
+
return this.processRows(batch, tasks, {
|
|
627
643
|
parallel,
|
|
628
644
|
disconnect() {
|
|
629
645
|
for (const item of clients) {
|
|
630
|
-
|
|
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
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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.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-
|
|
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.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 {
|
|
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 };
|