@naturalcycles/redis-lib 2.0.0 → 3.0.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/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/redisClient.d.ts +63 -0
- package/dist/redisClient.js +147 -0
- package/dist/redisKeyValueDB.d.ts +25 -0
- package/dist/redisKeyValueDB.js +106 -0
- package/package.json +14 -17
- package/readme.md +13 -19
- package/src/index.ts +2 -3
- package/src/redisClient.ts +217 -0
- package/src/redisKeyValueDB.ts +142 -0
- package/CHANGELOG.md +0 -32
- package/dist/index.js.map +0 -1
- package/dist/redis.db.d.ts +0 -45
- package/dist/redis.db.js +0 -120
- package/dist/redis.db.js.map +0 -1
- package/src/redis.db.ts +0 -183
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export * from './redisClient';
|
|
2
|
+
export * from './redisKeyValueDB';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./redisClient"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./redisKeyValueDB"), exports);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { CommonLogger, NullableBuffer, NullableString, Promisable, UnixTimestampNumber } from '@naturalcycles/js-lib';
|
|
3
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
+
import type { Redis, RedisOptions } from 'ioredis';
|
|
5
|
+
import type { ScanStreamOptions } from 'ioredis/built/types';
|
|
6
|
+
import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander';
|
|
7
|
+
export interface CommonClient extends AsyncDisposable {
|
|
8
|
+
connected: boolean;
|
|
9
|
+
connect: () => Promise<void>;
|
|
10
|
+
disconnect: () => Promise<void>;
|
|
11
|
+
ping: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface RedisClientCfg {
|
|
14
|
+
redisOptions?: RedisOptions;
|
|
15
|
+
/**
|
|
16
|
+
* Defaults to console.
|
|
17
|
+
*/
|
|
18
|
+
logger?: CommonLogger;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
Wraps the redis sdk with unified interface.
|
|
22
|
+
Features:
|
|
23
|
+
|
|
24
|
+
- Lazy loading & initialization
|
|
25
|
+
- Reasonable defaults
|
|
26
|
+
|
|
27
|
+
*/
|
|
28
|
+
export declare class RedisClient implements CommonClient {
|
|
29
|
+
constructor(cfg?: RedisClientCfg);
|
|
30
|
+
cfg: Required<RedisClientCfg>;
|
|
31
|
+
connected: boolean;
|
|
32
|
+
private _redis?;
|
|
33
|
+
redis(): Redis;
|
|
34
|
+
connect(): Promise<void>;
|
|
35
|
+
disconnect(): Promise<void>;
|
|
36
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
37
|
+
ping(): Promise<void>;
|
|
38
|
+
del(keys: string[]): Promise<number>;
|
|
39
|
+
get(key: string): Promise<NullableString>;
|
|
40
|
+
getBuffer(key: string): Promise<NullableBuffer>;
|
|
41
|
+
mget(keys: string[]): Promise<NullableString[]>;
|
|
42
|
+
mgetBuffer(keys: string[]): Promise<NullableBuffer[]>;
|
|
43
|
+
set(key: string, value: string | Buffer): Promise<void>;
|
|
44
|
+
setWithTTL(key: string, value: string | Buffer, expireAt: UnixTimestampNumber): Promise<void>;
|
|
45
|
+
mset(obj: Record<string, string>): Promise<void>;
|
|
46
|
+
msetBuffer(obj: Record<string, Buffer>): Promise<void>;
|
|
47
|
+
incr(key: string): Promise<number>;
|
|
48
|
+
ttl(key: string): Promise<number>;
|
|
49
|
+
dropTable(table: string): Promise<void>;
|
|
50
|
+
clearAll(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
Convenient type-safe wrapper.
|
|
53
|
+
Returns BATCHES of keys in each iteration (as-is).
|
|
54
|
+
*/
|
|
55
|
+
scanStream(opt: ScanStreamOptions): ReadableTyped<string[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Like scanStream, but flattens the stream of keys.
|
|
58
|
+
*/
|
|
59
|
+
scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string>;
|
|
60
|
+
scanCount(opt: ScanStreamOptions): Promise<number>;
|
|
61
|
+
withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void>;
|
|
62
|
+
private log;
|
|
63
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisClient = void 0;
|
|
4
|
+
/**
|
|
5
|
+
Wraps the redis sdk with unified interface.
|
|
6
|
+
Features:
|
|
7
|
+
|
|
8
|
+
- Lazy loading & initialization
|
|
9
|
+
- Reasonable defaults
|
|
10
|
+
|
|
11
|
+
*/
|
|
12
|
+
class RedisClient {
|
|
13
|
+
constructor(cfg = {}) {
|
|
14
|
+
this.connected = false;
|
|
15
|
+
this.cfg = {
|
|
16
|
+
logger: console,
|
|
17
|
+
...cfg,
|
|
18
|
+
redisOptions: {
|
|
19
|
+
showFriendlyErrorStack: true,
|
|
20
|
+
lazyConnect: true,
|
|
21
|
+
...cfg.redisOptions,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
redis() {
|
|
26
|
+
if (this._redis)
|
|
27
|
+
return this._redis;
|
|
28
|
+
// lazy-load the library
|
|
29
|
+
const redisLib = require('ioredis');
|
|
30
|
+
const redis = new redisLib.Redis(this.cfg.redisOptions);
|
|
31
|
+
const { logger } = this.cfg;
|
|
32
|
+
const redisEvents = ['connect', 'close', 'reconnecting', 'end'];
|
|
33
|
+
redisEvents.forEach(e => redis.on(e, () => logger.log(`redis: ${e}`)));
|
|
34
|
+
const closeEvents = ['SIGINT', 'SIGTERM'];
|
|
35
|
+
closeEvents.forEach(e => process.once(e, () => redis.quit()));
|
|
36
|
+
redis.on('error', err => logger.error(err));
|
|
37
|
+
this.connected = true;
|
|
38
|
+
this._redis = redis;
|
|
39
|
+
this.log(`redis: created`);
|
|
40
|
+
return redis;
|
|
41
|
+
}
|
|
42
|
+
async connect() {
|
|
43
|
+
if (!this.connected) {
|
|
44
|
+
await this.redis().connect();
|
|
45
|
+
this.connected = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async disconnect() {
|
|
49
|
+
this.log('redis: quit...');
|
|
50
|
+
this.log(`redis: quit`, await this.redis().quit());
|
|
51
|
+
this.connected = false;
|
|
52
|
+
}
|
|
53
|
+
async [Symbol.asyncDispose]() {
|
|
54
|
+
await this.disconnect();
|
|
55
|
+
}
|
|
56
|
+
async ping() {
|
|
57
|
+
await this.redis().ping();
|
|
58
|
+
}
|
|
59
|
+
async del(keys) {
|
|
60
|
+
return await this.redis().del(keys);
|
|
61
|
+
}
|
|
62
|
+
async get(key) {
|
|
63
|
+
return await this.redis().get(key);
|
|
64
|
+
}
|
|
65
|
+
async getBuffer(key) {
|
|
66
|
+
return await this.redis().getBuffer(key);
|
|
67
|
+
}
|
|
68
|
+
async mget(keys) {
|
|
69
|
+
return await this.redis().mget(keys);
|
|
70
|
+
}
|
|
71
|
+
async mgetBuffer(keys) {
|
|
72
|
+
return await this.redis().mgetBuffer(keys);
|
|
73
|
+
}
|
|
74
|
+
async set(key, value) {
|
|
75
|
+
await this.redis().set(key, value);
|
|
76
|
+
}
|
|
77
|
+
async setWithTTL(key, value, expireAt) {
|
|
78
|
+
await this.redis().set(key, value, 'EXAT', expireAt);
|
|
79
|
+
}
|
|
80
|
+
async mset(obj) {
|
|
81
|
+
await this.redis().mset(obj);
|
|
82
|
+
}
|
|
83
|
+
async msetBuffer(obj) {
|
|
84
|
+
await this.redis().mset(obj);
|
|
85
|
+
}
|
|
86
|
+
async incr(key) {
|
|
87
|
+
return await this.redis().incr(key);
|
|
88
|
+
}
|
|
89
|
+
async ttl(key) {
|
|
90
|
+
return await this.redis().ttl(key);
|
|
91
|
+
}
|
|
92
|
+
async dropTable(table) {
|
|
93
|
+
let count = 0;
|
|
94
|
+
await this.withPipeline(async (pipeline) => {
|
|
95
|
+
await this.scanStream({
|
|
96
|
+
match: `${table}:*`,
|
|
97
|
+
}).forEach(keys => {
|
|
98
|
+
pipeline.del(keys);
|
|
99
|
+
count += keys.length;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
this.log(`redis: dropped table ${table} (${count} keys)`);
|
|
103
|
+
}
|
|
104
|
+
async clearAll() {
|
|
105
|
+
this.log(`redis: clearAll...`);
|
|
106
|
+
let count = 0;
|
|
107
|
+
await this.withPipeline(async (pipeline) => {
|
|
108
|
+
await this.scanStream({
|
|
109
|
+
match: `*`,
|
|
110
|
+
}).forEach(keys => {
|
|
111
|
+
pipeline.del(keys);
|
|
112
|
+
count += keys.length;
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
this.log(`redis: clearAll removed ${count} keys`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
Convenient type-safe wrapper.
|
|
119
|
+
Returns BATCHES of keys in each iteration (as-is).
|
|
120
|
+
*/
|
|
121
|
+
scanStream(opt) {
|
|
122
|
+
return this.redis().scanStream(opt);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Like scanStream, but flattens the stream of keys.
|
|
126
|
+
*/
|
|
127
|
+
scanStreamFlat(opt) {
|
|
128
|
+
return this.redis().scanStream(opt).flatMap(keys => keys);
|
|
129
|
+
}
|
|
130
|
+
async scanCount(opt) {
|
|
131
|
+
// todo: implement more efficiently, e.g via LUA?
|
|
132
|
+
let count = 0;
|
|
133
|
+
await this.redis().scanStream(opt).forEach(keys => {
|
|
134
|
+
count += keys.length;
|
|
135
|
+
});
|
|
136
|
+
return count;
|
|
137
|
+
}
|
|
138
|
+
async withPipeline(fn) {
|
|
139
|
+
const pipeline = this.redis().pipeline();
|
|
140
|
+
await fn(pipeline);
|
|
141
|
+
await pipeline.exec();
|
|
142
|
+
}
|
|
143
|
+
log(...args) {
|
|
144
|
+
this.cfg.logger.log(...args);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.RedisClient = RedisClient;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { CommonKeyValueDBSaveBatchOptions, CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
+
import { RedisClient } from './redisClient';
|
|
5
|
+
export interface RedisKeyValueDBCfg {
|
|
6
|
+
client: RedisClient;
|
|
7
|
+
}
|
|
8
|
+
export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
9
|
+
constructor(cfg: RedisKeyValueDBCfg);
|
|
10
|
+
client: RedisClient;
|
|
11
|
+
ping(): Promise<void>;
|
|
12
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
13
|
+
getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
|
|
14
|
+
deleteByIds(table: string, ids: string[]): Promise<void>;
|
|
15
|
+
saveBatch(table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions): Promise<void>;
|
|
16
|
+
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
17
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
18
|
+
streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple>;
|
|
19
|
+
count(table: string): Promise<number>;
|
|
20
|
+
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
21
|
+
private idsToKeys;
|
|
22
|
+
private idToKey;
|
|
23
|
+
private keysToIds;
|
|
24
|
+
private keyToId;
|
|
25
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisKeyValueDB = void 0;
|
|
4
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
+
class RedisKeyValueDB {
|
|
6
|
+
constructor(cfg) {
|
|
7
|
+
this.client = cfg.client;
|
|
8
|
+
}
|
|
9
|
+
async ping() {
|
|
10
|
+
await this.client.ping();
|
|
11
|
+
}
|
|
12
|
+
async [Symbol.asyncDispose]() {
|
|
13
|
+
await this.client.disconnect();
|
|
14
|
+
}
|
|
15
|
+
async getByIds(table, ids) {
|
|
16
|
+
if (!ids.length)
|
|
17
|
+
return [];
|
|
18
|
+
// we assume that the order of returned values is the same as order of input ids
|
|
19
|
+
const bufs = await this.client.mgetBuffer(this.idsToKeys(table, ids));
|
|
20
|
+
return bufs.map((buf, i) => [ids[i], buf]).filter(([_k, v]) => v !== null);
|
|
21
|
+
}
|
|
22
|
+
async deleteByIds(table, ids) {
|
|
23
|
+
if (!ids.length)
|
|
24
|
+
return;
|
|
25
|
+
await this.client.del(this.idsToKeys(table, ids));
|
|
26
|
+
}
|
|
27
|
+
async saveBatch(table, entries, opt) {
|
|
28
|
+
if (!entries.length)
|
|
29
|
+
return;
|
|
30
|
+
if (opt?.expireAt) {
|
|
31
|
+
// There's no supported mset with TTL: https://stackoverflow.com/questions/16423342/redis-multi-set-with-a-ttl
|
|
32
|
+
// so we gonna use a pipeline instead
|
|
33
|
+
await this.client.withPipeline(pipeline => {
|
|
34
|
+
for (const [k, v] of entries) {
|
|
35
|
+
pipeline.set(this.idToKey(table, k), v, 'EXAT', opt.expireAt);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const obj = Object.fromEntries(entries.map(([k, v]) => [this.idToKey(table, k), v]));
|
|
41
|
+
await this.client.msetBuffer(obj);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
streamIds(table, limit) {
|
|
45
|
+
let stream = this.client
|
|
46
|
+
.scanStream({
|
|
47
|
+
match: `${table}:*`,
|
|
48
|
+
// count: limit, // count is actually a "batchSize", not a limit
|
|
49
|
+
})
|
|
50
|
+
.flatMap(keys => this.keysToIds(table, keys));
|
|
51
|
+
if (limit) {
|
|
52
|
+
stream = stream.take(limit);
|
|
53
|
+
}
|
|
54
|
+
return stream;
|
|
55
|
+
}
|
|
56
|
+
streamValues(table, limit) {
|
|
57
|
+
return this.client
|
|
58
|
+
.scanStream({
|
|
59
|
+
match: `${table}:*`,
|
|
60
|
+
})
|
|
61
|
+
.flatMap(async (keys) => {
|
|
62
|
+
return (await this.client.mgetBuffer(keys)).filter(js_lib_1._isTruthy);
|
|
63
|
+
}, {
|
|
64
|
+
concurrency: 16,
|
|
65
|
+
})
|
|
66
|
+
.take(limit || Infinity);
|
|
67
|
+
}
|
|
68
|
+
streamEntries(table, limit) {
|
|
69
|
+
return this.client
|
|
70
|
+
.scanStream({
|
|
71
|
+
match: `${table}:*`,
|
|
72
|
+
})
|
|
73
|
+
.flatMap(async (keys) => {
|
|
74
|
+
// casting as Buffer[], because values are expected to exist for given keys
|
|
75
|
+
const bufs = (await this.client.mgetBuffer(keys));
|
|
76
|
+
return (0, js_lib_1._zip)(this.keysToIds(table, keys), bufs);
|
|
77
|
+
}, {
|
|
78
|
+
concurrency: 16,
|
|
79
|
+
})
|
|
80
|
+
.take(limit || Infinity);
|
|
81
|
+
}
|
|
82
|
+
async count(table) {
|
|
83
|
+
// todo: implement more efficiently, e.g via LUA?
|
|
84
|
+
return await this.client.scanCount({
|
|
85
|
+
match: `${table}:*`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async createTable(table, opt) {
|
|
89
|
+
if (!opt?.dropIfExists)
|
|
90
|
+
return;
|
|
91
|
+
await this.client.dropTable(table);
|
|
92
|
+
}
|
|
93
|
+
idsToKeys(table, ids) {
|
|
94
|
+
return ids.map(id => this.idToKey(table, id));
|
|
95
|
+
}
|
|
96
|
+
idToKey(table, id) {
|
|
97
|
+
return `${table}:${id}`;
|
|
98
|
+
}
|
|
99
|
+
keysToIds(table, keys) {
|
|
100
|
+
return keys.map(key => this.keyToId(table, key));
|
|
101
|
+
}
|
|
102
|
+
keyToId(table, key) {
|
|
103
|
+
return key.slice(table.length + 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.RedisKeyValueDB = RedisKeyValueDB;
|
package/package.json
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/redis-lib",
|
|
3
|
-
"scripts": {
|
|
3
|
+
"scripts": {
|
|
4
|
+
"prepare": "husky"
|
|
5
|
+
},
|
|
4
6
|
"dependencies": {
|
|
5
|
-
"@naturalcycles/db-lib": "^
|
|
6
|
-
"@naturalcycles/js-lib": "^
|
|
7
|
-
"@naturalcycles/nodejs-lib": "^
|
|
8
|
-
"
|
|
9
|
-
"chalk": "^2.4.2",
|
|
10
|
-
"ioredis": "^4.14.0"
|
|
7
|
+
"@naturalcycles/db-lib": "^9.9.2",
|
|
8
|
+
"@naturalcycles/js-lib": "^14.217.0",
|
|
9
|
+
"@naturalcycles/nodejs-lib": "^13.8.0",
|
|
10
|
+
"ioredis": "^5.3.2"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@naturalcycles/dev-lib": "^
|
|
14
|
-
"@types/node": "^12.
|
|
15
|
-
"
|
|
16
|
-
"jest": "^24.9.0"
|
|
17
|
-
},
|
|
18
|
-
"resolutions": {
|
|
19
|
-
"@types/hapi__joi": "15.0.4"
|
|
13
|
+
"@naturalcycles/dev-lib": "^13.49.2",
|
|
14
|
+
"@types/node": "^20.12.2",
|
|
15
|
+
"jest": "^29.7.0"
|
|
20
16
|
},
|
|
21
17
|
"files": [
|
|
22
18
|
"dist",
|
|
@@ -29,6 +25,7 @@
|
|
|
29
25
|
"main": "dist/index.js",
|
|
30
26
|
"types": "dist/index.d.ts",
|
|
31
27
|
"publishConfig": {
|
|
28
|
+
"provenance": true,
|
|
32
29
|
"access": "public"
|
|
33
30
|
},
|
|
34
31
|
"repository": {
|
|
@@ -36,10 +33,10 @@
|
|
|
36
33
|
"url": "https://github.com/NaturalCycles/redis-lib"
|
|
37
34
|
},
|
|
38
35
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
36
|
+
"node": ">=18.12"
|
|
40
37
|
},
|
|
41
|
-
"version": "
|
|
42
|
-
"description": "Redis implementation of
|
|
38
|
+
"version": "3.0.0",
|
|
39
|
+
"description": "Redis implementation of CommonKeyValueDB interface",
|
|
43
40
|
"author": "Natural Cycles Team",
|
|
44
41
|
"license": "MIT"
|
|
45
42
|
}
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## @naturalcycles/redis-lib
|
|
2
2
|
|
|
3
|
-
> Redis implementation of
|
|
3
|
+
> Redis implementation of CommonKeyValueDB interface
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@naturalcycles/redis-lib)
|
|
6
6
|
[](https://github.com/prettier/prettier)
|
|
@@ -9,28 +9,22 @@
|
|
|
9
9
|
|
|
10
10
|
- ...
|
|
11
11
|
|
|
12
|
-
# DEBUG namespaces
|
|
13
|
-
|
|
14
|
-
- `nc:redis-lib`
|
|
15
|
-
- `ioredis`, `ioredis:redis`, `ioredis:connection`
|
|
16
|
-
|
|
17
|
-
# Packaging
|
|
18
|
-
|
|
19
|
-
- `engines.node >= 10.13`: Latest Node.js LTS
|
|
20
|
-
- `main: dist/index.js`: commonjs, es2018
|
|
21
|
-
- `types: dist/index.d.ts`: typescript types
|
|
22
|
-
- `/src` folder with source `*.ts` files included
|
|
23
|
-
|
|
24
12
|
# Starting / debugging Redis server on OSX
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
```shell
|
|
15
|
+
brew install redis
|
|
16
|
+
brew services start redis
|
|
17
|
+
brew services stop redis
|
|
18
|
+
|
|
19
|
+
redis-server /usr/local/etc/redis.conf
|
|
29
20
|
|
|
30
|
-
|
|
21
|
+
redis-cli ping
|
|
22
|
+
redis-cli flushall
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
# connect and list all keys
|
|
25
|
+
redis-cli
|
|
26
|
+
scan 0
|
|
27
|
+
```
|
|
34
28
|
|
|
35
29
|
Location and size of local DB:
|
|
36
30
|
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { RedisDBCfg, RedisDB }
|
|
1
|
+
export * from './redisClient'
|
|
2
|
+
export * from './redisKeyValueDB'
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommonLogger,
|
|
3
|
+
NullableBuffer,
|
|
4
|
+
NullableString,
|
|
5
|
+
Promisable,
|
|
6
|
+
UnixTimestampNumber,
|
|
7
|
+
} from '@naturalcycles/js-lib'
|
|
8
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
9
|
+
// eslint-disable-next-line import/no-duplicates
|
|
10
|
+
import type { Redis, RedisOptions } from 'ioredis'
|
|
11
|
+
// eslint-disable-next-line import/no-duplicates
|
|
12
|
+
import type * as RedisLib from 'ioredis'
|
|
13
|
+
import type { ScanStreamOptions } from 'ioredis/built/types'
|
|
14
|
+
import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander'
|
|
15
|
+
|
|
16
|
+
export interface CommonClient extends AsyncDisposable {
|
|
17
|
+
connected: boolean
|
|
18
|
+
connect: () => Promise<void>
|
|
19
|
+
disconnect: () => Promise<void>
|
|
20
|
+
ping: () => Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RedisClientCfg {
|
|
24
|
+
redisOptions?: RedisOptions
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Defaults to console.
|
|
28
|
+
*/
|
|
29
|
+
logger?: CommonLogger
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Wraps the redis sdk with unified interface.
|
|
34
|
+
Features:
|
|
35
|
+
|
|
36
|
+
- Lazy loading & initialization
|
|
37
|
+
- Reasonable defaults
|
|
38
|
+
|
|
39
|
+
*/
|
|
40
|
+
export class RedisClient implements CommonClient {
|
|
41
|
+
constructor(cfg: RedisClientCfg = {}) {
|
|
42
|
+
this.cfg = {
|
|
43
|
+
logger: console,
|
|
44
|
+
...cfg,
|
|
45
|
+
redisOptions: {
|
|
46
|
+
showFriendlyErrorStack: true,
|
|
47
|
+
lazyConnect: true,
|
|
48
|
+
...cfg.redisOptions,
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cfg!: Required<RedisClientCfg>
|
|
54
|
+
|
|
55
|
+
connected = false
|
|
56
|
+
|
|
57
|
+
private _redis?: Redis
|
|
58
|
+
|
|
59
|
+
redis(): Redis {
|
|
60
|
+
if (this._redis) return this._redis
|
|
61
|
+
|
|
62
|
+
// lazy-load the library
|
|
63
|
+
const redisLib = require('ioredis') as typeof RedisLib
|
|
64
|
+
const redis = new redisLib.Redis(this.cfg.redisOptions)
|
|
65
|
+
|
|
66
|
+
const { logger } = this.cfg
|
|
67
|
+
|
|
68
|
+
const redisEvents = ['connect', 'close', 'reconnecting', 'end']
|
|
69
|
+
redisEvents.forEach(e => redis.on(e, () => logger.log(`redis: ${e}`)))
|
|
70
|
+
|
|
71
|
+
const closeEvents: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
|
|
72
|
+
closeEvents.forEach(e => process.once(e, () => redis.quit()))
|
|
73
|
+
|
|
74
|
+
redis.on('error', err => logger.error(err))
|
|
75
|
+
|
|
76
|
+
this.connected = true
|
|
77
|
+
this._redis = redis
|
|
78
|
+
this.log(`redis: created`)
|
|
79
|
+
return redis
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async connect(): Promise<void> {
|
|
83
|
+
if (!this.connected) {
|
|
84
|
+
await this.redis().connect()
|
|
85
|
+
this.connected = true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async disconnect(): Promise<void> {
|
|
90
|
+
this.log('redis: quit...')
|
|
91
|
+
this.log(`redis: quit`, await this.redis().quit())
|
|
92
|
+
this.connected = false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async [Symbol.asyncDispose](): Promise<void> {
|
|
96
|
+
await this.disconnect()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async ping(): Promise<void> {
|
|
100
|
+
await this.redis().ping()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async del(keys: string[]): Promise<number> {
|
|
104
|
+
return await this.redis().del(keys)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async get(key: string): Promise<NullableString> {
|
|
108
|
+
return await this.redis().get(key)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getBuffer(key: string): Promise<NullableBuffer> {
|
|
112
|
+
return await this.redis().getBuffer(key)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async mget(keys: string[]): Promise<NullableString[]> {
|
|
116
|
+
return await this.redis().mget(keys)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async mgetBuffer(keys: string[]): Promise<NullableBuffer[]> {
|
|
120
|
+
return await this.redis().mgetBuffer(keys)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async set(key: string, value: string | Buffer): Promise<void> {
|
|
124
|
+
await this.redis().set(key, value)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async setWithTTL(
|
|
128
|
+
key: string,
|
|
129
|
+
value: string | Buffer,
|
|
130
|
+
expireAt: UnixTimestampNumber,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
await this.redis().set(key, value, 'EXAT', expireAt)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async mset(obj: Record<string, string>): Promise<void> {
|
|
136
|
+
await this.redis().mset(obj)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async msetBuffer(obj: Record<string, Buffer>): Promise<void> {
|
|
140
|
+
await this.redis().mset(obj)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async incr(key: string): Promise<number> {
|
|
144
|
+
return await this.redis().incr(key)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async ttl(key: string): Promise<number> {
|
|
148
|
+
return await this.redis().ttl(key)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async dropTable(table: string): Promise<void> {
|
|
152
|
+
let count = 0
|
|
153
|
+
|
|
154
|
+
await this.withPipeline(async pipeline => {
|
|
155
|
+
await this.scanStream({
|
|
156
|
+
match: `${table}:*`,
|
|
157
|
+
}).forEach(keys => {
|
|
158
|
+
pipeline.del(keys)
|
|
159
|
+
count += keys.length
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
this.log(`redis: dropped table ${table} (${count} keys)`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async clearAll(): Promise<void> {
|
|
167
|
+
this.log(`redis: clearAll...`)
|
|
168
|
+
let count = 0
|
|
169
|
+
|
|
170
|
+
await this.withPipeline(async pipeline => {
|
|
171
|
+
await this.scanStream({
|
|
172
|
+
match: `*`,
|
|
173
|
+
}).forEach(keys => {
|
|
174
|
+
pipeline.del(keys)
|
|
175
|
+
count += keys.length
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
this.log(`redis: clearAll removed ${count} keys`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
Convenient type-safe wrapper.
|
|
184
|
+
Returns BATCHES of keys in each iteration (as-is).
|
|
185
|
+
*/
|
|
186
|
+
scanStream(opt: ScanStreamOptions): ReadableTyped<string[]> {
|
|
187
|
+
return this.redis().scanStream(opt)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Like scanStream, but flattens the stream of keys.
|
|
192
|
+
*/
|
|
193
|
+
scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string> {
|
|
194
|
+
return (this.redis().scanStream(opt) as ReadableTyped<string[]>).flatMap(keys => keys)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async scanCount(opt: ScanStreamOptions): Promise<number> {
|
|
198
|
+
// todo: implement more efficiently, e.g via LUA?
|
|
199
|
+
let count = 0
|
|
200
|
+
|
|
201
|
+
await (this.redis().scanStream(opt) as ReadableTyped<string[]>).forEach(keys => {
|
|
202
|
+
count += keys.length
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return count
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void> {
|
|
209
|
+
const pipeline = this.redis().pipeline()
|
|
210
|
+
await fn(pipeline)
|
|
211
|
+
await pipeline.exec()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private log(...args: any[]): void {
|
|
215
|
+
this.cfg.logger.log(...args)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommonKeyValueDBSaveBatchOptions,
|
|
3
|
+
CommonDBCreateOptions,
|
|
4
|
+
CommonKeyValueDB,
|
|
5
|
+
KeyValueDBTuple,
|
|
6
|
+
} from '@naturalcycles/db-lib'
|
|
7
|
+
import { _isTruthy, _zip } from '@naturalcycles/js-lib'
|
|
8
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
9
|
+
import { RedisClient } from './redisClient'
|
|
10
|
+
|
|
11
|
+
export interface RedisKeyValueDBCfg {
|
|
12
|
+
client: RedisClient
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
16
|
+
constructor(cfg: RedisKeyValueDBCfg) {
|
|
17
|
+
this.client = cfg.client
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
client: RedisClient
|
|
21
|
+
|
|
22
|
+
async ping(): Promise<void> {
|
|
23
|
+
await this.client.ping()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async [Symbol.asyncDispose](): Promise<void> {
|
|
27
|
+
await this.client.disconnect()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
31
|
+
if (!ids.length) return []
|
|
32
|
+
// we assume that the order of returned values is the same as order of input ids
|
|
33
|
+
const bufs = await this.client.mgetBuffer(this.idsToKeys(table, ids))
|
|
34
|
+
return bufs.map((buf, i) => [ids[i], buf] as KeyValueDBTuple).filter(([_k, v]) => v !== null)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
38
|
+
if (!ids.length) return
|
|
39
|
+
await this.client.del(this.idsToKeys(table, ids))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async saveBatch(
|
|
43
|
+
table: string,
|
|
44
|
+
entries: KeyValueDBTuple[],
|
|
45
|
+
opt?: CommonKeyValueDBSaveBatchOptions,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
if (!entries.length) return
|
|
48
|
+
|
|
49
|
+
if (opt?.expireAt) {
|
|
50
|
+
// There's no supported mset with TTL: https://stackoverflow.com/questions/16423342/redis-multi-set-with-a-ttl
|
|
51
|
+
// so we gonna use a pipeline instead
|
|
52
|
+
await this.client.withPipeline(pipeline => {
|
|
53
|
+
for (const [k, v] of entries) {
|
|
54
|
+
pipeline.set(this.idToKey(table, k), v, 'EXAT', opt.expireAt!)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
} else {
|
|
58
|
+
const obj: Record<string, Buffer> = Object.fromEntries(
|
|
59
|
+
entries.map(([k, v]) => [this.idToKey(table, k), v]) as KeyValueDBTuple[],
|
|
60
|
+
)
|
|
61
|
+
await this.client.msetBuffer(obj)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
66
|
+
let stream = this.client
|
|
67
|
+
.scanStream({
|
|
68
|
+
match: `${table}:*`,
|
|
69
|
+
// count: limit, // count is actually a "batchSize", not a limit
|
|
70
|
+
})
|
|
71
|
+
.flatMap(keys => this.keysToIds(table, keys))
|
|
72
|
+
|
|
73
|
+
if (limit) {
|
|
74
|
+
stream = stream.take(limit)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return stream
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
81
|
+
return this.client
|
|
82
|
+
.scanStream({
|
|
83
|
+
match: `${table}:*`,
|
|
84
|
+
})
|
|
85
|
+
.flatMap(
|
|
86
|
+
async keys => {
|
|
87
|
+
return (await this.client.mgetBuffer(keys)).filter(_isTruthy)
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
concurrency: 16,
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
.take(limit || Infinity)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple> {
|
|
97
|
+
return this.client
|
|
98
|
+
.scanStream({
|
|
99
|
+
match: `${table}:*`,
|
|
100
|
+
})
|
|
101
|
+
.flatMap(
|
|
102
|
+
async keys => {
|
|
103
|
+
// casting as Buffer[], because values are expected to exist for given keys
|
|
104
|
+
const bufs = (await this.client.mgetBuffer(keys)) as Buffer[]
|
|
105
|
+
return _zip(this.keysToIds(table, keys), bufs)
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
concurrency: 16,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
.take(limit || Infinity)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async count(table: string): Promise<number> {
|
|
115
|
+
// todo: implement more efficiently, e.g via LUA?
|
|
116
|
+
return await this.client.scanCount({
|
|
117
|
+
match: `${table}:*`,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
|
|
122
|
+
if (!opt?.dropIfExists) return
|
|
123
|
+
|
|
124
|
+
await this.client.dropTable(table)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private idsToKeys(table: string, ids: string[]): string[] {
|
|
128
|
+
return ids.map(id => this.idToKey(table, id))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private idToKey(table: string, id: string): string {
|
|
132
|
+
return `${table}:${id}`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private keysToIds(table: string, keys: string[]): string[] {
|
|
136
|
+
return keys.map(key => this.keyToId(table, key))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private keyToId(table: string, key: string): string {
|
|
140
|
+
return key.slice(table.length + 1)
|
|
141
|
+
}
|
|
142
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# [2.0.0](https://github.com/NaturalCycles/redis-lib/compare/v1.1.1...v2.0.0) (2019-10-19)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
### Features
|
|
5
|
-
|
|
6
|
-
* impl CommonDB 2.0 ([4830225](https://github.com/NaturalCycles/redis-lib/commit/4830225))
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### BREAKING CHANGES
|
|
10
|
-
|
|
11
|
-
* ^^^
|
|
12
|
-
|
|
13
|
-
## [1.1.1](https://github.com/NaturalCycles/redis-lib/compare/v1.1.0...v1.1.1) (2019-09-20)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Bug Fixes
|
|
17
|
-
|
|
18
|
-
* adapt to new db-lib ([d3555be](https://github.com/NaturalCycles/redis-lib/commit/d3555be))
|
|
19
|
-
|
|
20
|
-
# [1.1.0](https://github.com/NaturalCycles/redis-lib/compare/v1.0.0...v1.1.0) (2019-08-23)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
### Features
|
|
24
|
-
|
|
25
|
-
* namespacePrefix; adopt to db-lib ([e3b35f4](https://github.com/NaturalCycles/redis-lib/commit/e3b35f4))
|
|
26
|
-
|
|
27
|
-
# 1.0.0 (2019-08-20)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### Features
|
|
31
|
-
|
|
32
|
-
* first version ([a80ee79](https://github.com/NaturalCycles/redis-lib/commit/a80ee79))
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yCAAgD;AAE3B,kBAFZ,kBAAO,CAEY"}
|
package/dist/redis.db.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { CommonDB, CommonDBOptions, CommonDBSaveOptions, DBQuery, RunQueryResult, SavedDBEntity } from '@naturalcycles/db-lib';
|
|
3
|
-
import { RedisOptions } from 'ioredis';
|
|
4
|
-
import * as Redis from 'ioredis';
|
|
5
|
-
import { Readable } from 'stream';
|
|
6
|
-
export interface RedisDBCfg {
|
|
7
|
-
redisOptions?: RedisOptions;
|
|
8
|
-
/**
|
|
9
|
-
* If true - it will "emulate" queries by using SCAN $table_
|
|
10
|
-
* which will load ALL table keys into memory and filter in-memory.
|
|
11
|
-
* @default false
|
|
12
|
-
*/
|
|
13
|
-
runQueries?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* If set - all keys will be prefixed by it.
|
|
16
|
-
* So the key will look like:
|
|
17
|
-
* ${namespacePrefix}${table}_${id}
|
|
18
|
-
*/
|
|
19
|
-
namespacePrefix?: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* streamQuery doesn't support limit and order - it always returns unlimited unsorted results.
|
|
23
|
-
*/
|
|
24
|
-
export declare class RedisDB implements CommonDB {
|
|
25
|
-
constructor(cfg?: RedisDBCfg);
|
|
26
|
-
cfg: Required<RedisDBCfg>;
|
|
27
|
-
redis: Redis.Redis;
|
|
28
|
-
protected create(): Redis.Redis;
|
|
29
|
-
quit(): Promise<void>;
|
|
30
|
-
resetCache(table?: string): Promise<void>;
|
|
31
|
-
key(table: string, id: string): string;
|
|
32
|
-
parseKey(table: string, key: string): {
|
|
33
|
-
table: string;
|
|
34
|
-
id: string;
|
|
35
|
-
};
|
|
36
|
-
serialize<T extends object>(obj: T): string;
|
|
37
|
-
deserialize<T = any>(s?: string | null): T;
|
|
38
|
-
saveBatch<DBM extends SavedDBEntity>(table: string, dbms: DBM[], opts?: CommonDBSaveOptions): Promise<void>;
|
|
39
|
-
getByIds<DBM extends SavedDBEntity>(table: string, ids: string[], opts?: CommonDBOptions): Promise<DBM[]>;
|
|
40
|
-
deleteByIds(table: string, ids: string[], opts?: CommonDBOptions): Promise<number>;
|
|
41
|
-
streamQuery<DBM extends SavedDBEntity>(q: DBQuery<DBM>, opts?: CommonDBOptions): Readable;
|
|
42
|
-
runQuery<DBM extends SavedDBEntity>(q: DBQuery<DBM>, opts?: CommonDBOptions): Promise<RunQueryResult<DBM>>;
|
|
43
|
-
runQueryCount<DBM extends SavedDBEntity>(q: DBQuery<DBM>, opts?: CommonDBOptions): Promise<number>;
|
|
44
|
-
deleteByQuery<DBM extends SavedDBEntity>(q: DBQuery<DBM>, opts?: CommonDBOptions): Promise<number>;
|
|
45
|
-
}
|
package/dist/redis.db.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const inMemory_db_1 = require("@naturalcycles/db-lib/dist/inMemory.db");
|
|
4
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
-
const chalk_1 = require("chalk");
|
|
7
|
-
const Redis = require("ioredis");
|
|
8
|
-
const stream_1 = require("stream");
|
|
9
|
-
const log = nodejs_lib_1.Debug('nc:redis-lib');
|
|
10
|
-
/**
|
|
11
|
-
* streamQuery doesn't support limit and order - it always returns unlimited unsorted results.
|
|
12
|
-
*/
|
|
13
|
-
class RedisDB {
|
|
14
|
-
constructor(cfg = {}) {
|
|
15
|
-
this.cfg = {
|
|
16
|
-
runQueries: false,
|
|
17
|
-
namespacePrefix: '',
|
|
18
|
-
...cfg,
|
|
19
|
-
redisOptions: {
|
|
20
|
-
showFriendlyErrorStack: true,
|
|
21
|
-
lazyConnect: true,
|
|
22
|
-
...cfg.redisOptions,
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
this.redis = this.create();
|
|
26
|
-
}
|
|
27
|
-
create() {
|
|
28
|
-
const redis = new Redis(this.cfg.redisOptions);
|
|
29
|
-
const redisEvents = ['connect', 'close', 'reconnecting', 'end'];
|
|
30
|
-
redisEvents.forEach(e => redis.on(e, () => log(`event:`, chalk_1.default.bold(e))));
|
|
31
|
-
const closeEvents = ['SIGINT', 'SIGTERM'];
|
|
32
|
-
closeEvents.forEach(e => process.once(e, () => redis.quit()));
|
|
33
|
-
redis.on('error', err => log.error(err));
|
|
34
|
-
// log('connected')
|
|
35
|
-
return redis;
|
|
36
|
-
}
|
|
37
|
-
async quit() {
|
|
38
|
-
log('disconnecting...');
|
|
39
|
-
log(`quit:`, await this.redis.quit());
|
|
40
|
-
}
|
|
41
|
-
async resetCache(table) {
|
|
42
|
-
const pattern = `${this.cfg.namespacePrefix}${table || ''}*`;
|
|
43
|
-
const keys = await this.redis.keys(pattern);
|
|
44
|
-
if (keys.length) {
|
|
45
|
-
await this.redis.del(...keys);
|
|
46
|
-
}
|
|
47
|
-
log(`resetCache deleted ${keys.length} keys under ${pattern}`);
|
|
48
|
-
}
|
|
49
|
-
key(table, id) {
|
|
50
|
-
return this.cfg.namespacePrefix + [table, id].join('_');
|
|
51
|
-
}
|
|
52
|
-
parseKey(table, key) {
|
|
53
|
-
return {
|
|
54
|
-
table,
|
|
55
|
-
id: key.substr(this.cfg.namespacePrefix.length + table.length + 1),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
serialize(obj) {
|
|
59
|
-
return JSON.stringify(obj);
|
|
60
|
-
}
|
|
61
|
-
deserialize(s) {
|
|
62
|
-
try {
|
|
63
|
-
return s && JSON.parse(s);
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
log.error(s, typeof s, err);
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async saveBatch(table, dbms, opts) {
|
|
71
|
-
if (!dbms.length)
|
|
72
|
-
return;
|
|
73
|
-
await this.redis.mset(js_lib_1._flatten(dbms.map(dbm => [this.key(table, dbm.id), this.serialize(dbm)])));
|
|
74
|
-
}
|
|
75
|
-
async getByIds(table, ids, opts) {
|
|
76
|
-
if (!ids.length)
|
|
77
|
-
return [];
|
|
78
|
-
const dbms = (await this.redis.mget(...ids.map(id => this.key(table, id))));
|
|
79
|
-
return dbms.filter(Boolean).map(dbm => this.deserialize(dbm));
|
|
80
|
-
}
|
|
81
|
-
async deleteByIds(table, ids, opts) {
|
|
82
|
-
if (!ids.length)
|
|
83
|
-
return 0;
|
|
84
|
-
return await this.redis.del(...ids.map(id => this.key(table, id)));
|
|
85
|
-
}
|
|
86
|
-
streamQuery(q, opts) {
|
|
87
|
-
if (!this.cfg.runQueries)
|
|
88
|
-
return nodejs_lib_1.readableFrom([]);
|
|
89
|
-
const _this = this;
|
|
90
|
-
return this.redis
|
|
91
|
-
.scanStream({
|
|
92
|
-
match: `${this.cfg.namespacePrefix}${q.table}_*`,
|
|
93
|
-
})
|
|
94
|
-
.pipe(new stream_1.Transform({
|
|
95
|
-
objectMode: true,
|
|
96
|
-
async transform(keys, _encoding, cb) {
|
|
97
|
-
const ids = keys.map(k => _this.parseKey(q.table, k).id);
|
|
98
|
-
const dbms = await _this.getByIds(q.table, ids);
|
|
99
|
-
const items = inMemory_db_1.queryInMemory(q, dbms);
|
|
100
|
-
// tslint:disable-next-line:no-invalid-this
|
|
101
|
-
items.forEach(item => this.push(item)); // push multiple items!
|
|
102
|
-
cb();
|
|
103
|
-
},
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
async runQuery(q, opts) {
|
|
107
|
-
const dbms = await nodejs_lib_1.streamToArray(this.streamQuery(q, opts));
|
|
108
|
-
return { records: inMemory_db_1.queryInMemory(q, dbms) };
|
|
109
|
-
}
|
|
110
|
-
async runQueryCount(q, opts) {
|
|
111
|
-
const { records } = await this.runQuery(q, opts);
|
|
112
|
-
return records.length;
|
|
113
|
-
}
|
|
114
|
-
async deleteByQuery(q, opts) {
|
|
115
|
-
const { records } = await this.runQuery(q, opts);
|
|
116
|
-
return await this.deleteByIds(q.table, records.map(dbm => dbm.id), opts);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
exports.RedisDB = RedisDB;
|
|
120
|
-
//# sourceMappingURL=redis.db.js.map
|
package/dist/redis.db.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redis.db.js","sourceRoot":"","sources":["../src/redis.db.ts"],"names":[],"mappings":"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"}
|
package/src/redis.db.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CommonDB,
|
|
3
|
-
CommonDBOptions,
|
|
4
|
-
CommonDBSaveOptions,
|
|
5
|
-
DBQuery,
|
|
6
|
-
RunQueryResult,
|
|
7
|
-
SavedDBEntity,
|
|
8
|
-
} from '@naturalcycles/db-lib'
|
|
9
|
-
import { queryInMemory } from '@naturalcycles/db-lib/dist/inMemory.db'
|
|
10
|
-
import { _flatten } from '@naturalcycles/js-lib'
|
|
11
|
-
import { Debug, readableFrom, streamToArray } from '@naturalcycles/nodejs-lib'
|
|
12
|
-
import c from 'chalk'
|
|
13
|
-
import { RedisOptions } from 'ioredis'
|
|
14
|
-
import * as Redis from 'ioredis'
|
|
15
|
-
import { Readable, Transform } from 'stream'
|
|
16
|
-
|
|
17
|
-
const log = Debug('nc:redis-lib')
|
|
18
|
-
|
|
19
|
-
export interface RedisDBCfg {
|
|
20
|
-
redisOptions?: RedisOptions
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* If true - it will "emulate" queries by using SCAN $table_
|
|
24
|
-
* which will load ALL table keys into memory and filter in-memory.
|
|
25
|
-
* @default false
|
|
26
|
-
*/
|
|
27
|
-
runQueries?: boolean
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* If set - all keys will be prefixed by it.
|
|
31
|
-
* So the key will look like:
|
|
32
|
-
* ${namespacePrefix}${table}_${id}
|
|
33
|
-
*/
|
|
34
|
-
namespacePrefix?: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* streamQuery doesn't support limit and order - it always returns unlimited unsorted results.
|
|
39
|
-
*/
|
|
40
|
-
export class RedisDB implements CommonDB {
|
|
41
|
-
constructor(cfg: RedisDBCfg = {}) {
|
|
42
|
-
this.cfg = {
|
|
43
|
-
runQueries: false,
|
|
44
|
-
namespacePrefix: '',
|
|
45
|
-
...cfg,
|
|
46
|
-
redisOptions: {
|
|
47
|
-
showFriendlyErrorStack: true,
|
|
48
|
-
lazyConnect: true,
|
|
49
|
-
...cfg.redisOptions,
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
this.redis = this.create()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
public cfg!: Required<RedisDBCfg>
|
|
57
|
-
|
|
58
|
-
redis!: Redis.Redis
|
|
59
|
-
|
|
60
|
-
protected create(): Redis.Redis {
|
|
61
|
-
const redis = new Redis(this.cfg.redisOptions)
|
|
62
|
-
|
|
63
|
-
const redisEvents = ['connect', 'close', 'reconnecting', 'end']
|
|
64
|
-
redisEvents.forEach(e => redis.on(e, () => log(`event:`, c.bold(e))))
|
|
65
|
-
|
|
66
|
-
const closeEvents: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
|
|
67
|
-
closeEvents.forEach(e => process.once(e, () => redis.quit()))
|
|
68
|
-
|
|
69
|
-
redis.on('error', err => log.error(err))
|
|
70
|
-
|
|
71
|
-
// log('connected')
|
|
72
|
-
return redis
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async quit(): Promise<void> {
|
|
76
|
-
log('disconnecting...')
|
|
77
|
-
log(`quit:`, await this.redis.quit())
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async resetCache(table?: string): Promise<void> {
|
|
81
|
-
const pattern = `${this.cfg.namespacePrefix}${table || ''}*`
|
|
82
|
-
const keys = await this.redis.keys(pattern)
|
|
83
|
-
if (keys.length) {
|
|
84
|
-
await this.redis.del(...keys)
|
|
85
|
-
}
|
|
86
|
-
log(`resetCache deleted ${keys.length} keys under ${pattern}`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
key(table: string, id: string): string {
|
|
90
|
-
return this.cfg.namespacePrefix + [table, id].join('_')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
parseKey(table: string, key: string): { table: string; id: string } {
|
|
94
|
-
return {
|
|
95
|
-
table,
|
|
96
|
-
id: key.substr(this.cfg.namespacePrefix.length + table.length + 1),
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
serialize<T extends object>(obj: T): string {
|
|
101
|
-
return JSON.stringify(obj)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
deserialize<T = any>(s?: string | null): T {
|
|
105
|
-
try {
|
|
106
|
-
return s && JSON.parse(s)
|
|
107
|
-
} catch (err) {
|
|
108
|
-
log.error(s, typeof s, err)
|
|
109
|
-
return undefined as any
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async saveBatch<DBM extends SavedDBEntity>(
|
|
114
|
-
table: string,
|
|
115
|
-
dbms: DBM[],
|
|
116
|
-
opts?: CommonDBSaveOptions,
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
if (!dbms.length) return
|
|
119
|
-
await this.redis.mset(_flatten(dbms.map(dbm => [this.key(table, dbm.id), this.serialize(dbm)])))
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async getByIds<DBM extends SavedDBEntity>(
|
|
123
|
-
table: string,
|
|
124
|
-
ids: string[],
|
|
125
|
-
opts?: CommonDBOptions,
|
|
126
|
-
): Promise<DBM[]> {
|
|
127
|
-
if (!ids.length) return []
|
|
128
|
-
const dbms = (await this.redis.mget(...ids.map(id => this.key(table, id)))) as string[]
|
|
129
|
-
return dbms.filter(Boolean).map(dbm => this.deserialize<DBM>(dbm))
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async deleteByIds(table: string, ids: string[], opts?: CommonDBOptions): Promise<number> {
|
|
133
|
-
if (!ids.length) return 0
|
|
134
|
-
return await this.redis.del(...ids.map(id => this.key(table, id)))
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
streamQuery<DBM extends SavedDBEntity>(q: DBQuery<DBM>, opts?: CommonDBOptions): Readable {
|
|
138
|
-
if (!this.cfg.runQueries) return readableFrom([])
|
|
139
|
-
const _this = this
|
|
140
|
-
|
|
141
|
-
return this.redis
|
|
142
|
-
.scanStream({
|
|
143
|
-
match: `${this.cfg.namespacePrefix}${q.table}_*`,
|
|
144
|
-
})
|
|
145
|
-
.pipe(
|
|
146
|
-
new Transform({
|
|
147
|
-
objectMode: true,
|
|
148
|
-
async transform(keys: string[], _encoding, cb) {
|
|
149
|
-
const ids = keys.map(k => _this.parseKey(q.table, k).id)
|
|
150
|
-
const dbms = await _this.getByIds<DBM>(q.table, ids)
|
|
151
|
-
const items = queryInMemory(q, dbms)
|
|
152
|
-
// tslint:disable-next-line:no-invalid-this
|
|
153
|
-
items.forEach(item => this.push(item)) // push multiple items!
|
|
154
|
-
cb()
|
|
155
|
-
},
|
|
156
|
-
}),
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async runQuery<DBM extends SavedDBEntity>(
|
|
161
|
-
q: DBQuery<DBM>,
|
|
162
|
-
opts?: CommonDBOptions,
|
|
163
|
-
): Promise<RunQueryResult<DBM>> {
|
|
164
|
-
const dbms = await streamToArray<DBM>(this.streamQuery(q, opts))
|
|
165
|
-
return { records: queryInMemory(q, dbms) }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async runQueryCount<DBM extends SavedDBEntity>(
|
|
169
|
-
q: DBQuery<DBM>,
|
|
170
|
-
opts?: CommonDBOptions,
|
|
171
|
-
): Promise<number> {
|
|
172
|
-
const { records } = await this.runQuery(q, opts)
|
|
173
|
-
return records.length
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async deleteByQuery<DBM extends SavedDBEntity>(
|
|
177
|
-
q: DBQuery<DBM>,
|
|
178
|
-
opts?: CommonDBOptions,
|
|
179
|
-
): Promise<number> {
|
|
180
|
-
const { records } = await this.runQuery(q, opts)
|
|
181
|
-
return await this.deleteByIds(q.table, records.map(dbm => dbm.id), opts)
|
|
182
|
-
}
|
|
183
|
-
}
|