@naturalcycles/redis-lib 4.5.0 → 4.6.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/dist/redisClient.d.ts +20 -0
- package/dist/redisClient.js +53 -0
- package/package.json +3 -3
- package/src/redisClient.ts +65 -0
package/dist/redisClient.d.ts
CHANGED
|
@@ -53,8 +53,28 @@ export declare class RedisClient implements CommonClient {
|
|
|
53
53
|
hsetWithTTL(_key: string, _value: AnyObject, _expireAt: UnixTimestamp): Promise<void>;
|
|
54
54
|
mset(obj: Record<string, string | number>): Promise<void>;
|
|
55
55
|
msetBuffer(obj: Record<string, Buffer>): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* For counters that are supposed to expire, use {@link incrWithTTL} instead.
|
|
58
|
+
*/
|
|
56
59
|
incr(key: string, by?: number): Promise<number>;
|
|
60
|
+
/**
|
|
61
|
+
* Increments the key and guarantees it has an expiry, in a single transaction.
|
|
62
|
+
*
|
|
63
|
+
* Returns the new value.
|
|
64
|
+
*
|
|
65
|
+
* Requires Redis 7.0+
|
|
66
|
+
*/
|
|
67
|
+
incrWithTTL(key: string, expireAt: UnixTimestamp, by?: number): Promise<number>;
|
|
68
|
+
/**
|
|
69
|
+
* For counters that are supposed to expire, use {@link incrBatchWithTTL} instead.
|
|
70
|
+
*/
|
|
57
71
|
incrBatch(incrementTuples: [string, number][]): Promise<[string, number][]>;
|
|
72
|
+
/**
|
|
73
|
+
* Batch version of {@link incrWithTTL}, all increments and expiries in a single transaction.
|
|
74
|
+
*
|
|
75
|
+
* Requires Redis 7.0+
|
|
76
|
+
*/
|
|
77
|
+
incrBatchWithTTL(incrementTuples: [string, number][], expireAt: UnixTimestamp): Promise<[string, number][]>;
|
|
58
78
|
ttl(key: string): Promise<number>;
|
|
59
79
|
dropTable(table: string): Promise<void>;
|
|
60
80
|
clearAll(): Promise<void>;
|
package/dist/redisClient.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _assert } from '@naturalcycles/js-lib/error';
|
|
1
2
|
import { _stringMapEntries } from '@naturalcycles/js-lib/types';
|
|
2
3
|
import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
|
|
3
4
|
/**
|
|
@@ -149,10 +150,37 @@ export class RedisClient {
|
|
|
149
150
|
const redis = await this.redis();
|
|
150
151
|
await redis.mset(obj);
|
|
151
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* For counters that are supposed to expire, use {@link incrWithTTL} instead.
|
|
155
|
+
*/
|
|
152
156
|
async incr(key, by = 1) {
|
|
153
157
|
const redis = await this.redis();
|
|
154
158
|
return await redis.incrby(key, by);
|
|
155
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Increments the key and guarantees it has an expiry, in a single transaction.
|
|
162
|
+
*
|
|
163
|
+
* Returns the new value.
|
|
164
|
+
*
|
|
165
|
+
* Requires Redis 7.0+
|
|
166
|
+
*/
|
|
167
|
+
async incrWithTTL(key, expireAt, by = 1) {
|
|
168
|
+
const redis = await this.redis();
|
|
169
|
+
const results = await redis.multi().incrby(key, by).expireat(key, expireAt, 'NX').exec();
|
|
170
|
+
const result = results?.[0];
|
|
171
|
+
_assert(result, `redis: incrWithTTL transaction returned no result, key: ${key}`);
|
|
172
|
+
// Redis does not roll back, so a failed EXPIREAT leaves the key incremented and without an
|
|
173
|
+
// expiry. It self-heals on the next successful call, as EXPIREAT NX applies to a TTL-less key.
|
|
174
|
+
for (const [err] of results) {
|
|
175
|
+
if (err)
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
const [, value] = result;
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* For counters that are supposed to expire, use {@link incrBatchWithTTL} instead.
|
|
183
|
+
*/
|
|
156
184
|
async incrBatch(incrementTuples) {
|
|
157
185
|
const results = {};
|
|
158
186
|
await this.withPipeline(async (pipeline) => {
|
|
@@ -165,6 +193,31 @@ export class RedisClient {
|
|
|
165
193
|
const validResults = _stringMapEntries(results).filter(([_, v]) => v !== undefined);
|
|
166
194
|
return validResults;
|
|
167
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Batch version of {@link incrWithTTL}, all increments and expiries in a single transaction.
|
|
198
|
+
*
|
|
199
|
+
* Requires Redis 7.0+
|
|
200
|
+
*/
|
|
201
|
+
async incrBatchWithTTL(incrementTuples, expireAt) {
|
|
202
|
+
const redis = await this.redis();
|
|
203
|
+
const multi = redis.multi();
|
|
204
|
+
for (const [key, increment] of incrementTuples) {
|
|
205
|
+
multi.incrby(key, increment);
|
|
206
|
+
multi.expireat(key, expireAt, 'NX');
|
|
207
|
+
}
|
|
208
|
+
// 2 commands are queued per key, so the increments sit at the even indexes
|
|
209
|
+
const results = await multi.exec();
|
|
210
|
+
const expectedLength = incrementTuples.length * 2;
|
|
211
|
+
_assert(results?.length === expectedLength, `redis: incrBatchWithTTL expected ${expectedLength} results, got ${results?.length}`);
|
|
212
|
+
for (const [err] of results) {
|
|
213
|
+
if (err)
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
return incrementTuples.map(([key], i) => {
|
|
217
|
+
const [, newValue] = results[i * 2];
|
|
218
|
+
return [key, newValue];
|
|
219
|
+
});
|
|
220
|
+
}
|
|
168
221
|
async ttl(key) {
|
|
169
222
|
const redis = await this.redis();
|
|
170
223
|
return await redis.ttl(key);
|
package/package.json
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"tslib": "^2"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
+
"@naturalcycles/dev-lib": "0.0.0",
|
|
12
13
|
"@types/node": "^26",
|
|
13
|
-
"typescript": "^7"
|
|
14
|
-
"@naturalcycles/dev-lib": "0.0.0"
|
|
14
|
+
"typescript": "^7"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
17
|
".": "./dist/index.js"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=24.10.0"
|
|
40
40
|
},
|
|
41
|
-
"version": "4.
|
|
41
|
+
"version": "4.6.0",
|
|
42
42
|
"description": "Redis implementation of CommonKeyValueDB interface",
|
|
43
43
|
"author": "Natural Cycles Team",
|
|
44
44
|
"license": "MIT",
|
package/src/redisClient.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _assert } from '@naturalcycles/js-lib/error'
|
|
1
2
|
import type { CommonLogger } from '@naturalcycles/js-lib/log'
|
|
2
3
|
import type {
|
|
3
4
|
AnyObject,
|
|
@@ -221,11 +222,40 @@ export class RedisClient implements CommonClient {
|
|
|
221
222
|
await redis.mset(obj)
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
/**
|
|
226
|
+
* For counters that are supposed to expire, use {@link incrWithTTL} instead.
|
|
227
|
+
*/
|
|
224
228
|
async incr(key: string, by = 1): Promise<number> {
|
|
225
229
|
const redis = await this.redis()
|
|
226
230
|
return await redis.incrby(key, by)
|
|
227
231
|
}
|
|
228
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Increments the key and guarantees it has an expiry, in a single transaction.
|
|
235
|
+
*
|
|
236
|
+
* Returns the new value.
|
|
237
|
+
*
|
|
238
|
+
* Requires Redis 7.0+
|
|
239
|
+
*/
|
|
240
|
+
async incrWithTTL(key: string, expireAt: UnixTimestamp, by = 1): Promise<number> {
|
|
241
|
+
const redis = await this.redis()
|
|
242
|
+
const results = await redis.multi().incrby(key, by).expireat(key, expireAt, 'NX').exec()
|
|
243
|
+
const result = results?.[0]
|
|
244
|
+
_assert(result, `redis: incrWithTTL transaction returned no result, key: ${key}`)
|
|
245
|
+
|
|
246
|
+
// Redis does not roll back, so a failed EXPIREAT leaves the key incremented and without an
|
|
247
|
+
// expiry. It self-heals on the next successful call, as EXPIREAT NX applies to a TTL-less key.
|
|
248
|
+
for (const [err] of results) {
|
|
249
|
+
if (err) throw err
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const [, value] = result
|
|
253
|
+
return value as number
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* For counters that are supposed to expire, use {@link incrBatchWithTTL} instead.
|
|
258
|
+
*/
|
|
229
259
|
async incrBatch(incrementTuples: [string, number][]): Promise<[string, number][]> {
|
|
230
260
|
const results: StringMap<number | undefined> = {}
|
|
231
261
|
|
|
@@ -245,6 +275,41 @@ export class RedisClient implements CommonClient {
|
|
|
245
275
|
return validResults
|
|
246
276
|
}
|
|
247
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Batch version of {@link incrWithTTL}, all increments and expiries in a single transaction.
|
|
280
|
+
*
|
|
281
|
+
* Requires Redis 7.0+
|
|
282
|
+
*/
|
|
283
|
+
async incrBatchWithTTL(
|
|
284
|
+
incrementTuples: [string, number][],
|
|
285
|
+
expireAt: UnixTimestamp,
|
|
286
|
+
): Promise<[string, number][]> {
|
|
287
|
+
const redis = await this.redis()
|
|
288
|
+
const multi = redis.multi()
|
|
289
|
+
|
|
290
|
+
for (const [key, increment] of incrementTuples) {
|
|
291
|
+
multi.incrby(key, increment)
|
|
292
|
+
multi.expireat(key, expireAt, 'NX')
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 2 commands are queued per key, so the increments sit at the even indexes
|
|
296
|
+
const results = await multi.exec()
|
|
297
|
+
const expectedLength = incrementTuples.length * 2
|
|
298
|
+
_assert(
|
|
299
|
+
results?.length === expectedLength,
|
|
300
|
+
`redis: incrBatchWithTTL expected ${expectedLength} results, got ${results?.length}`,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
for (const [err] of results) {
|
|
304
|
+
if (err) throw err
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return incrementTuples.map(([key], i) => {
|
|
308
|
+
const [, newValue] = results[i * 2]!
|
|
309
|
+
return [key, newValue as number]
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
248
313
|
async ttl(key: string): Promise<number> {
|
|
249
314
|
const redis = await this.redis()
|
|
250
315
|
return await redis.ttl(key)
|