@cap-js-community/common 0.3.0 → 0.3.2
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/CHANGELOG.md +12 -0
- package/README.md +4 -4
- package/cds-plugin.js +1 -25
- package/package.json +2 -2
- package/src/migration-check/MigrationCheck.js +1 -1
- package/src/rate-limiting/RateLimiting.js +1 -1
- package/src/rate-limiting/redis/common.js +3 -3
- package/src/redis-client/RedisClient.js +64 -40
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## Version 0.3.2 - 2025-11-05
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Rework redis client
|
|
13
|
+
|
|
14
|
+
## Version 0.3.1 - 2025-11-05
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Rework redis client
|
|
19
|
+
|
|
8
20
|
## Version 0.3.0 - 2025-11-03
|
|
9
21
|
|
|
10
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -328,14 +328,14 @@ A Redis Client broker is provided to connect to Redis service.
|
|
|
328
328
|
|
|
329
329
|
```js
|
|
330
330
|
const { RedisClient } = require("@cap-js-community/common");
|
|
331
|
-
const mainClient = await RedisClient.
|
|
331
|
+
const mainClient = await RedisClient.create().createMainClientAndConnect(options);
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
#### Main named singleton
|
|
335
335
|
|
|
336
336
|
```js
|
|
337
337
|
const { RedisClient } = require("@cap-js-community/common");
|
|
338
|
-
const mainClient = await RedisClient.
|
|
338
|
+
const mainClient = await RedisClient.create("name").createMainClientAndConnect(options);
|
|
339
339
|
```
|
|
340
340
|
|
|
341
341
|
#### Custom named
|
|
@@ -384,14 +384,14 @@ Specific Redis options for a custom name can be established as follows:
|
|
|
384
384
|
|
|
385
385
|
```js
|
|
386
386
|
const { RedisClient } = require("@cap-js-community/common");
|
|
387
|
-
const mainClient = await RedisClient.
|
|
387
|
+
const mainClient = await RedisClient.create("customName").createMainClientAndConnect(options);
|
|
388
388
|
```
|
|
389
389
|
|
|
390
390
|
In addition, options can be passed to Redis client during creation via `options` parameter:
|
|
391
391
|
|
|
392
392
|
```js
|
|
393
393
|
const { RedisClient } = require("@cap-js-community/common");
|
|
394
|
-
const mainClient = await RedisClient.
|
|
394
|
+
const mainClient = await RedisClient.create().createMainClientAndConnect(options);
|
|
395
395
|
```
|
|
396
396
|
|
|
397
397
|
For details on Redis `createClient` configuration options see [Redis Client Configuration](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).
|
package/cds-plugin.js
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
4
|
require("./src/common/promise");
|
|
5
5
|
|
|
6
|
-
const { ReplicationCache, RateLimiting
|
|
7
|
-
|
|
8
|
-
const TIMEOUT_SHUTDOWN = 2500;
|
|
6
|
+
const { ReplicationCache, RateLimiting } = require("./src");
|
|
9
7
|
|
|
10
8
|
if (cds.env.rateLimiting.plugin) {
|
|
11
9
|
cds.on("serving", async (service) => {
|
|
@@ -24,25 +22,3 @@ if (cds.env.rateLimiting.plugin) {
|
|
|
24
22
|
if (cds.env.replicationCache.plugin) {
|
|
25
23
|
cds.replicationCache = new ReplicationCache();
|
|
26
24
|
}
|
|
27
|
-
|
|
28
|
-
cds.on("shutdown", async () => {
|
|
29
|
-
await shutdownWebSocketServer();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
async function shutdownWebSocketServer() {
|
|
33
|
-
return await new Promise((resolve, reject) => {
|
|
34
|
-
const timeoutRef = setTimeout(() => {
|
|
35
|
-
clearTimeout(timeoutRef);
|
|
36
|
-
resolve();
|
|
37
|
-
}, TIMEOUT_SHUTDOWN);
|
|
38
|
-
RedisClient.closeAllClients()
|
|
39
|
-
.then((result) => {
|
|
40
|
-
clearTimeout(timeoutRef);
|
|
41
|
-
resolve(result);
|
|
42
|
-
})
|
|
43
|
-
.catch((err) => {
|
|
44
|
-
clearTimeout(timeoutRef);
|
|
45
|
-
reject(err);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/common",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@sap/cds": "^9.4.3",
|
|
57
57
|
"@sap/cds-common-content": "^3.0.1",
|
|
58
58
|
"@sap/cds-dk": "^9.4.1",
|
|
59
|
-
"eslint": "^9.
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
60
|
"eslint-config-prettier": "^10.1.8",
|
|
61
61
|
"eslint-plugin-jest": "^29.0.1",
|
|
62
62
|
"eslint-plugin-n": "^17.23.1",
|
|
@@ -5,7 +5,7 @@ const path = require("path");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const crypto = require("crypto");
|
|
7
7
|
|
|
8
|
-
const COMPONENT_NAME = "migrationCheck";
|
|
8
|
+
const COMPONENT_NAME = "/cap-js-community-common/migrationCheck";
|
|
9
9
|
const STRING_DEFAULT_LENGTH = 5000;
|
|
10
10
|
|
|
11
11
|
const Checks = [releasedEntityCheck, newEntityCheck, uniqueIndexCheck, journalModeCheck];
|
|
@@ -7,7 +7,7 @@ const redisResetTime = require("./redis/resetTime");
|
|
|
7
7
|
|
|
8
8
|
const { connectionCheck } = require("./redis/common");
|
|
9
9
|
|
|
10
|
-
const COMPONENT_NAME = "rateLimiting";
|
|
10
|
+
const COMPONENT_NAME = "/cap-js-community-common/rateLimiting";
|
|
11
11
|
|
|
12
12
|
class RateLimiting {
|
|
13
13
|
constructor(service, { maxConcurrent, maxInWindow, window } = {}) {
|
|
@@ -4,14 +4,14 @@ const cds = require("@sap/cds");
|
|
|
4
4
|
|
|
5
5
|
const { RedisClient } = require("../../redis-client");
|
|
6
6
|
|
|
7
|
-
const COMPONENT_NAME = "rateLimiting";
|
|
7
|
+
const COMPONENT_NAME = "/cap-js-community-common/rateLimiting";
|
|
8
8
|
|
|
9
9
|
async function connectionCheck() {
|
|
10
|
-
return await RedisClient.
|
|
10
|
+
return await RedisClient.create(COMPONENT_NAME).connectionCheck();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function perform(key, cb, cbFallback, retry = cds.env.rateLimiting.retry) {
|
|
14
|
-
const client = cds.env.rateLimiting.redis && (await RedisClient.
|
|
14
|
+
const client = cds.env.rateLimiting.redis && (await RedisClient.create(COMPONENT_NAME).createMainClientAndConnect());
|
|
15
15
|
if (client) {
|
|
16
16
|
const value = await cb(client, key);
|
|
17
17
|
if (value === undefined) {
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const redis = require("redis");
|
|
4
|
+
const cds = require("@sap/cds");
|
|
4
5
|
|
|
5
|
-
const COMPONENT_NAME = "redisClient";
|
|
6
|
+
const COMPONENT_NAME = "/cap-js-community-common/redisClient";
|
|
6
7
|
const LOG_AFTER_SEC = 5;
|
|
8
|
+
const TIMEOUT_SHUTDOWN = 2500;
|
|
7
9
|
|
|
8
10
|
class RedisClient {
|
|
9
|
-
|
|
11
|
+
#clusterClient = false;
|
|
12
|
+
#beforeCloseHandler;
|
|
13
|
+
constructor(name, env) {
|
|
10
14
|
this.name = name;
|
|
15
|
+
this.env = env || this.name;
|
|
11
16
|
this.log = cds.log(COMPONENT_NAME);
|
|
12
17
|
this.mainClientPromise = null;
|
|
13
|
-
this.additionalClientPromise = null;
|
|
14
18
|
this.subscriberClientPromise = null;
|
|
15
19
|
this.subscribedChannels = {};
|
|
16
20
|
this.lastErrorLog = Date.now();
|
|
21
|
+
|
|
22
|
+
if (!RedisClient._shutdownRegistered) {
|
|
23
|
+
RedisClient._shutdownRegistered = true;
|
|
24
|
+
cds.on("shutdown", async () => {
|
|
25
|
+
await this.closeRedisClients();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
createMainClientAndConnect(options) {
|
|
@@ -33,19 +44,8 @@ class RedisClient {
|
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
createAdditionalClientAndConnect(options) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const errorHandlerCreateClient = (err) => {
|
|
41
|
-
this.additionalClientPromise?.then?.(this.resilientClientClose);
|
|
42
|
-
this.log.error("Error from additional redis client", err);
|
|
43
|
-
this.additionalClientPromise = null;
|
|
44
|
-
setTimeout(() => this.createAdditionalClientAndConnect(options), LOG_AFTER_SEC * 1000).unref();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
this.additionalClientPromise = this.createClientAndConnect(options, errorHandlerCreateClient);
|
|
48
|
-
return this.additionalClientPromise;
|
|
47
|
+
const redisClient = RedisClient.create(this.name + "-2", this.env);
|
|
48
|
+
return redisClient.createMainClientAndConnect(options);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async createClientAndConnect(options, errorHandlerCreateClient, isConnectionCheck) {
|
|
@@ -92,7 +92,8 @@ class RedisClient {
|
|
|
92
92
|
throw error;
|
|
93
93
|
}
|
|
94
94
|
if (client) {
|
|
95
|
-
|
|
95
|
+
// NOTE: ignore promise: client should not wait + fn can't throw
|
|
96
|
+
this.resilientClientClose(client);
|
|
96
97
|
return true;
|
|
97
98
|
}
|
|
98
99
|
} catch (err) {
|
|
@@ -103,7 +104,7 @@ class RedisClient {
|
|
|
103
104
|
|
|
104
105
|
createClientBase(redisOptions = {}) {
|
|
105
106
|
const { credentials, options } =
|
|
106
|
-
(this.
|
|
107
|
+
(this.env ? cds.env.requires[`redis-${this.env}`] : undefined) || cds.env.requires["redis"] || {};
|
|
107
108
|
const socket = {
|
|
108
109
|
host: credentials?.hostname ?? "127.0.0.1",
|
|
109
110
|
tls: !!credentials?.tls,
|
|
@@ -119,6 +120,7 @@ class RedisClient {
|
|
|
119
120
|
};
|
|
120
121
|
try {
|
|
121
122
|
if (credentials?.cluster_mode) {
|
|
123
|
+
this.#clusterClient = true;
|
|
122
124
|
return redis.createCluster({
|
|
123
125
|
rootNodes: [socketOptions],
|
|
124
126
|
defaults: socketOptions,
|
|
@@ -141,14 +143,14 @@ class RedisClient {
|
|
|
141
143
|
LOG_AFTER_SEC * 1000,
|
|
142
144
|
).unref();
|
|
143
145
|
};
|
|
144
|
-
this.subscribeChannels(options,
|
|
146
|
+
this.subscribeChannels(options, errorHandlerCreateClient);
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
subscribeChannels(options,
|
|
149
|
+
subscribeChannels(options, errorHandlerCreateClient) {
|
|
148
150
|
this.subscriberClientPromise = this.createClientAndConnect(options, errorHandlerCreateClient)
|
|
149
151
|
.then((client) => {
|
|
150
152
|
for (const channel in this.subscribedChannels) {
|
|
151
|
-
const fn = subscribedChannels[channel];
|
|
153
|
+
const fn = this.subscribedChannels[channel];
|
|
152
154
|
client._subscribedChannels ??= {};
|
|
153
155
|
if (client._subscribedChannels[channel]) {
|
|
154
156
|
continue;
|
|
@@ -193,16 +195,6 @@ class RedisClient {
|
|
|
193
195
|
this.log.info("Main redis client closed!");
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
async closeAdditionalClient() {
|
|
197
|
-
if (!this.additionalClientPromise) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const client = this.additionalClientPromise;
|
|
201
|
-
this.additionalClientPromise = null;
|
|
202
|
-
await this.resilientClientClose(await client);
|
|
203
|
-
this.log.info("Additional redis client closed!");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
198
|
async closeSubscribeClient() {
|
|
207
199
|
if (!this.subscriberClientPromise) {
|
|
208
200
|
return;
|
|
@@ -214,9 +206,10 @@ class RedisClient {
|
|
|
214
206
|
}
|
|
215
207
|
|
|
216
208
|
async closeClients() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
209
|
+
if (this.#beforeCloseHandler) {
|
|
210
|
+
await this.#beforeCloseHandler();
|
|
211
|
+
}
|
|
212
|
+
await Promise.allSettled([this.closeMainClient(), this.closeSubscribeClient()]);
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
async resilientClientClose(client) {
|
|
@@ -229,16 +222,47 @@ class RedisClient {
|
|
|
229
222
|
}
|
|
230
223
|
}
|
|
231
224
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
225
|
+
async closeRedisClients() {
|
|
226
|
+
return await new Promise((resolve, reject) => {
|
|
227
|
+
const timeoutRef = setTimeout(() => {
|
|
228
|
+
clearTimeout(timeoutRef);
|
|
229
|
+
resolve();
|
|
230
|
+
}, TIMEOUT_SHUTDOWN);
|
|
231
|
+
RedisClient.closeAllClients()
|
|
232
|
+
.then((result) => {
|
|
233
|
+
clearTimeout(timeoutRef);
|
|
234
|
+
resolve(result);
|
|
235
|
+
})
|
|
236
|
+
.catch((err) => {
|
|
237
|
+
clearTimeout(timeoutRef);
|
|
238
|
+
reject(err);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
set beforeCloseHandler(cb) {
|
|
244
|
+
this.#beforeCloseHandler = cb;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get isCluster() {
|
|
248
|
+
return this.#clusterClient;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static create(name = "default", env) {
|
|
252
|
+
env ??= name;
|
|
253
|
+
RedisClient._create ??= {};
|
|
254
|
+
if (!RedisClient._create[name]) {
|
|
255
|
+
RedisClient._create[name] = new RedisClient(name, env);
|
|
236
256
|
}
|
|
237
|
-
return RedisClient.
|
|
257
|
+
return RedisClient._create[name];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static default(name) {
|
|
261
|
+
return RedisClient.create(name);
|
|
238
262
|
}
|
|
239
263
|
|
|
240
264
|
static async closeAllClients() {
|
|
241
|
-
for (const entry of Object.values(RedisClient.
|
|
265
|
+
for (const entry of Object.values(RedisClient._create || {})) {
|
|
242
266
|
await entry.closeClients();
|
|
243
267
|
}
|
|
244
268
|
}
|