@graphql-mesh/cache-redis 0.104.13 → 0.105.0-alpha-20251010093057-4eeae9412ec39cfbab6e001ecaf527cf94f4ea31
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/cjs/index.js +122 -100
- package/esm/index.js +122 -100
- package/package.json +2 -1
- package/typings/index.d.cts +1 -0
- package/typings/index.d.ts +1 -0
package/cjs/index.js
CHANGED
|
@@ -6,128 +6,150 @@ const ioredis_mock_1 = tslib_1.__importDefault(require("ioredis-mock"));
|
|
|
6
6
|
const cross_helpers_1 = require("@graphql-mesh/cross-helpers");
|
|
7
7
|
const string_interpolation_1 = require("@graphql-mesh/string-interpolation");
|
|
8
8
|
const types_1 = require("@graphql-mesh/types");
|
|
9
|
+
const api_1 = require("@opentelemetry/api");
|
|
9
10
|
const disposablestack_1 = require("@whatwg-node/disposablestack");
|
|
10
11
|
function interpolateStrWithEnv(str) {
|
|
11
12
|
return string_interpolation_1.stringInterpolator.parse(str, { env: cross_helpers_1.process.env });
|
|
12
13
|
}
|
|
13
14
|
class RedisCache {
|
|
14
15
|
constructor(options) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
16
|
+
this.tracer = api_1.trace.getTracer('hive.cache.redis');
|
|
17
|
+
this.tracer.startActiveSpan('hive.cache.redis.init', span => {
|
|
18
|
+
try {
|
|
19
|
+
const lazyConnect = options.lazyConnect !== false;
|
|
20
|
+
if ('startupNodes' in options) {
|
|
21
|
+
const parsedUsername = interpolateStrWithEnv(options.username?.toString()) || cross_helpers_1.process.env.REDIS_USERNAME;
|
|
22
|
+
const parsedPassword = interpolateStrWithEnv(options.password?.toString()) || cross_helpers_1.process.env.REDIS_PASSWORD;
|
|
23
|
+
const parsedDb = interpolateStrWithEnv(options.db?.toString()) || cross_helpers_1.process.env.REDIS_DB;
|
|
24
|
+
const numDb = parseInt(parsedDb);
|
|
25
|
+
this.client = new ioredis_1.default.Cluster(options.startupNodes.map(s => ({
|
|
26
|
+
host: s.host && interpolateStrWithEnv(s.host),
|
|
27
|
+
port: s.port && parseInt(interpolateStrWithEnv(s.port)),
|
|
28
|
+
family: s.family && parseInt(interpolateStrWithEnv(s.family)),
|
|
29
|
+
})), {
|
|
30
|
+
dnsLookup: options.dnsLookupAsIs
|
|
31
|
+
? (address, callback) => callback(null, address)
|
|
32
|
+
: undefined,
|
|
33
|
+
redisOptions: {
|
|
34
|
+
username: parsedUsername,
|
|
35
|
+
password: parsedPassword,
|
|
36
|
+
db: isNaN(numDb) ? undefined : numDb,
|
|
37
|
+
enableAutoPipelining: true,
|
|
38
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
39
|
+
tls: options.tls ? {} : undefined,
|
|
40
|
+
},
|
|
41
|
+
enableAutoPipelining: true,
|
|
42
|
+
enableOfflineQueue: true,
|
|
43
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else if ('sentinels' in options) {
|
|
47
|
+
this.client = new ioredis_1.default({
|
|
48
|
+
name: options.name,
|
|
49
|
+
sentinelPassword: options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword),
|
|
50
|
+
sentinels: options.sentinels.map(s => ({
|
|
51
|
+
host: s.host && interpolateStrWithEnv(s.host),
|
|
52
|
+
port: s.port && parseInt(interpolateStrWithEnv(s.port)),
|
|
53
|
+
family: s.family && parseInt(interpolateStrWithEnv(s.family)),
|
|
54
|
+
})),
|
|
55
|
+
role: options.role,
|
|
56
|
+
enableTLSForSentinelMode: options.enableTLSForSentinelMode,
|
|
57
|
+
enableAutoPipelining: true,
|
|
58
|
+
enableOfflineQueue: true,
|
|
59
|
+
lazyConnect,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else if (options.url) {
|
|
63
|
+
const redisUrl = new URL(interpolateStrWithEnv(options.url));
|
|
64
|
+
if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) {
|
|
65
|
+
throw new Error('Redis URL must use either redis:// or rediss://');
|
|
66
|
+
}
|
|
67
|
+
if (lazyConnect) {
|
|
68
|
+
redisUrl.searchParams.set('lazyConnect', 'true');
|
|
69
|
+
}
|
|
70
|
+
redisUrl.searchParams.set('enableAutoPipelining', 'true');
|
|
71
|
+
redisUrl.searchParams.set('enableOfflineQueue', 'true');
|
|
72
|
+
const IPV6_REGEX = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/gm;
|
|
73
|
+
if (IPV6_REGEX.test(redisUrl.hostname)) {
|
|
74
|
+
redisUrl.searchParams.set('family', '6');
|
|
75
|
+
}
|
|
76
|
+
const urlStr = redisUrl.toString();
|
|
77
|
+
options.logger.debug(`Connecting to Redis at ${urlStr}`);
|
|
78
|
+
this.client = new ioredis_1.default(urlStr);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const parsedHost = interpolateStrWithEnv(options.host?.toString()) || cross_helpers_1.process.env.REDIS_HOST;
|
|
82
|
+
const parsedPort = interpolateStrWithEnv(options.port?.toString()) || cross_helpers_1.process.env.REDIS_PORT;
|
|
83
|
+
const parsedUsername = interpolateStrWithEnv(options.username?.toString()) || cross_helpers_1.process.env.REDIS_USERNAME;
|
|
84
|
+
const parsedPassword = interpolateStrWithEnv(options.password?.toString()) || cross_helpers_1.process.env.REDIS_PASSWORD;
|
|
85
|
+
const parsedDb = interpolateStrWithEnv(options.db?.toString()) || cross_helpers_1.process.env.REDIS_DB;
|
|
86
|
+
const parsedFamily = interpolateStrWithEnv(options.family?.toString()) || cross_helpers_1.process.env.REDIS_FAMILY;
|
|
87
|
+
const numPort = parseInt(parsedPort);
|
|
88
|
+
const numDb = parseInt(parsedDb);
|
|
89
|
+
if (parsedHost) {
|
|
90
|
+
options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`);
|
|
91
|
+
this.client = new ioredis_1.default({
|
|
92
|
+
host: parsedHost,
|
|
93
|
+
port: isNaN(numPort) ? undefined : numPort,
|
|
94
|
+
username: parsedUsername,
|
|
95
|
+
password: parsedPassword,
|
|
96
|
+
db: isNaN(numDb) ? undefined : numDb,
|
|
97
|
+
family: parsedFamily === '6' ? 6 : undefined,
|
|
98
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
99
|
+
enableAutoPipelining: true,
|
|
100
|
+
enableOfflineQueue: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
options.logger.debug(`Connecting to Redis mock`);
|
|
105
|
+
this.client = new ioredis_mock_1.default();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const pubsub = (0, types_1.toMeshPubSub)(options.pubsub);
|
|
109
|
+
// TODO: PubSub.destroy will no longer be needed after v0
|
|
110
|
+
const id = pubsub?.subscribe('destroy', () => {
|
|
111
|
+
this.client.disconnect(false);
|
|
112
|
+
pubsub.unsubscribe(id);
|
|
97
113
|
});
|
|
98
114
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.client = new ioredis_mock_1.default();
|
|
115
|
+
finally {
|
|
116
|
+
span.end();
|
|
102
117
|
}
|
|
103
|
-
}
|
|
104
|
-
const pubsub = (0, types_1.toMeshPubSub)(options.pubsub);
|
|
105
|
-
// TODO: PubSub.destroy will no longer be needed after v0
|
|
106
|
-
const id = pubsub?.subscribe('destroy', () => {
|
|
107
|
-
this.client.disconnect(false);
|
|
108
|
-
pubsub.unsubscribe(id);
|
|
109
118
|
});
|
|
110
119
|
}
|
|
111
120
|
[disposablestack_1.DisposableSymbols.dispose]() {
|
|
112
121
|
this.client.disconnect(false);
|
|
113
122
|
}
|
|
114
123
|
set(key, value, options) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
return this.tracer.startActiveSpan('hive.cache.set', async (span) => {
|
|
125
|
+
try {
|
|
126
|
+
const stringifiedValue = JSON.stringify(value);
|
|
127
|
+
if (options?.ttl && options.ttl > 0) {
|
|
128
|
+
return await this.client.set(key, stringifiedValue, 'PX', options.ttl * 1000);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return await this.client.set(key, stringifiedValue);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
span.end();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
122
138
|
}
|
|
123
139
|
get(key) {
|
|
124
|
-
return this.
|
|
140
|
+
return this.tracer.startActiveSpan('hive.cache.get', span => this.client
|
|
141
|
+
.get(key)
|
|
142
|
+
.then(value => (value != null ? JSON.parse(value) : undefined))
|
|
143
|
+
.finally(() => span.end()));
|
|
125
144
|
}
|
|
126
145
|
getKeysByPrefix(prefix) {
|
|
127
146
|
return scanPatterns(this.client, `${prefix}*`);
|
|
128
147
|
}
|
|
129
148
|
delete(key) {
|
|
130
|
-
return this.
|
|
149
|
+
return this.tracer.startActiveSpan('hive.cache.delete', span => this.client
|
|
150
|
+
.del(key)
|
|
151
|
+
.then(value => value > 0, () => false)
|
|
152
|
+
.finally(() => span.end()));
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
155
|
exports.default = RedisCache;
|
package/esm/index.js
CHANGED
|
@@ -3,128 +3,150 @@ import RedisMock from 'ioredis-mock';
|
|
|
3
3
|
import { process } from '@graphql-mesh/cross-helpers';
|
|
4
4
|
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
|
|
5
5
|
import { toMeshPubSub, } from '@graphql-mesh/types';
|
|
6
|
+
import { trace } from '@opentelemetry/api';
|
|
6
7
|
import { DisposableSymbols } from '@whatwg-node/disposablestack';
|
|
7
8
|
function interpolateStrWithEnv(str) {
|
|
8
9
|
return stringInterpolator.parse(str, { env: process.env });
|
|
9
10
|
}
|
|
10
11
|
export default class RedisCache {
|
|
11
12
|
constructor(options) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
13
|
+
this.tracer = trace.getTracer('hive.cache.redis');
|
|
14
|
+
this.tracer.startActiveSpan('hive.cache.redis.init', span => {
|
|
15
|
+
try {
|
|
16
|
+
const lazyConnect = options.lazyConnect !== false;
|
|
17
|
+
if ('startupNodes' in options) {
|
|
18
|
+
const parsedUsername = interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME;
|
|
19
|
+
const parsedPassword = interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD;
|
|
20
|
+
const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB;
|
|
21
|
+
const numDb = parseInt(parsedDb);
|
|
22
|
+
this.client = new Redis.Cluster(options.startupNodes.map(s => ({
|
|
23
|
+
host: s.host && interpolateStrWithEnv(s.host),
|
|
24
|
+
port: s.port && parseInt(interpolateStrWithEnv(s.port)),
|
|
25
|
+
family: s.family && parseInt(interpolateStrWithEnv(s.family)),
|
|
26
|
+
})), {
|
|
27
|
+
dnsLookup: options.dnsLookupAsIs
|
|
28
|
+
? (address, callback) => callback(null, address)
|
|
29
|
+
: undefined,
|
|
30
|
+
redisOptions: {
|
|
31
|
+
username: parsedUsername,
|
|
32
|
+
password: parsedPassword,
|
|
33
|
+
db: isNaN(numDb) ? undefined : numDb,
|
|
34
|
+
enableAutoPipelining: true,
|
|
35
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
36
|
+
tls: options.tls ? {} : undefined,
|
|
37
|
+
},
|
|
38
|
+
enableAutoPipelining: true,
|
|
39
|
+
enableOfflineQueue: true,
|
|
40
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else if ('sentinels' in options) {
|
|
44
|
+
this.client = new Redis({
|
|
45
|
+
name: options.name,
|
|
46
|
+
sentinelPassword: options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword),
|
|
47
|
+
sentinels: options.sentinels.map(s => ({
|
|
48
|
+
host: s.host && interpolateStrWithEnv(s.host),
|
|
49
|
+
port: s.port && parseInt(interpolateStrWithEnv(s.port)),
|
|
50
|
+
family: s.family && parseInt(interpolateStrWithEnv(s.family)),
|
|
51
|
+
})),
|
|
52
|
+
role: options.role,
|
|
53
|
+
enableTLSForSentinelMode: options.enableTLSForSentinelMode,
|
|
54
|
+
enableAutoPipelining: true,
|
|
55
|
+
enableOfflineQueue: true,
|
|
56
|
+
lazyConnect,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (options.url) {
|
|
60
|
+
const redisUrl = new URL(interpolateStrWithEnv(options.url));
|
|
61
|
+
if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) {
|
|
62
|
+
throw new Error('Redis URL must use either redis:// or rediss://');
|
|
63
|
+
}
|
|
64
|
+
if (lazyConnect) {
|
|
65
|
+
redisUrl.searchParams.set('lazyConnect', 'true');
|
|
66
|
+
}
|
|
67
|
+
redisUrl.searchParams.set('enableAutoPipelining', 'true');
|
|
68
|
+
redisUrl.searchParams.set('enableOfflineQueue', 'true');
|
|
69
|
+
const IPV6_REGEX = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/gm;
|
|
70
|
+
if (IPV6_REGEX.test(redisUrl.hostname)) {
|
|
71
|
+
redisUrl.searchParams.set('family', '6');
|
|
72
|
+
}
|
|
73
|
+
const urlStr = redisUrl.toString();
|
|
74
|
+
options.logger.debug(`Connecting to Redis at ${urlStr}`);
|
|
75
|
+
this.client = new Redis(urlStr);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const parsedHost = interpolateStrWithEnv(options.host?.toString()) || process.env.REDIS_HOST;
|
|
79
|
+
const parsedPort = interpolateStrWithEnv(options.port?.toString()) || process.env.REDIS_PORT;
|
|
80
|
+
const parsedUsername = interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME;
|
|
81
|
+
const parsedPassword = interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD;
|
|
82
|
+
const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB;
|
|
83
|
+
const parsedFamily = interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY;
|
|
84
|
+
const numPort = parseInt(parsedPort);
|
|
85
|
+
const numDb = parseInt(parsedDb);
|
|
86
|
+
if (parsedHost) {
|
|
87
|
+
options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`);
|
|
88
|
+
this.client = new Redis({
|
|
89
|
+
host: parsedHost,
|
|
90
|
+
port: isNaN(numPort) ? undefined : numPort,
|
|
91
|
+
username: parsedUsername,
|
|
92
|
+
password: parsedPassword,
|
|
93
|
+
db: isNaN(numDb) ? undefined : numDb,
|
|
94
|
+
family: parsedFamily === '6' ? 6 : undefined,
|
|
95
|
+
...(lazyConnect ? { lazyConnect: true } : {}),
|
|
96
|
+
enableAutoPipelining: true,
|
|
97
|
+
enableOfflineQueue: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
options.logger.debug(`Connecting to Redis mock`);
|
|
102
|
+
this.client = new RedisMock();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const pubsub = toMeshPubSub(options.pubsub);
|
|
106
|
+
// TODO: PubSub.destroy will no longer be needed after v0
|
|
107
|
+
const id = pubsub?.subscribe('destroy', () => {
|
|
108
|
+
this.client.disconnect(false);
|
|
109
|
+
pubsub.unsubscribe(id);
|
|
94
110
|
});
|
|
95
111
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.client = new RedisMock();
|
|
112
|
+
finally {
|
|
113
|
+
span.end();
|
|
99
114
|
}
|
|
100
|
-
}
|
|
101
|
-
const pubsub = toMeshPubSub(options.pubsub);
|
|
102
|
-
// TODO: PubSub.destroy will no longer be needed after v0
|
|
103
|
-
const id = pubsub?.subscribe('destroy', () => {
|
|
104
|
-
this.client.disconnect(false);
|
|
105
|
-
pubsub.unsubscribe(id);
|
|
106
115
|
});
|
|
107
116
|
}
|
|
108
117
|
[DisposableSymbols.dispose]() {
|
|
109
118
|
this.client.disconnect(false);
|
|
110
119
|
}
|
|
111
120
|
set(key, value, options) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
return this.tracer.startActiveSpan('hive.cache.set', async (span) => {
|
|
122
|
+
try {
|
|
123
|
+
const stringifiedValue = JSON.stringify(value);
|
|
124
|
+
if (options?.ttl && options.ttl > 0) {
|
|
125
|
+
return await this.client.set(key, stringifiedValue, 'PX', options.ttl * 1000);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return await this.client.set(key, stringifiedValue);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
span.end();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
119
135
|
}
|
|
120
136
|
get(key) {
|
|
121
|
-
return this.
|
|
137
|
+
return this.tracer.startActiveSpan('hive.cache.get', span => this.client
|
|
138
|
+
.get(key)
|
|
139
|
+
.then(value => (value != null ? JSON.parse(value) : undefined))
|
|
140
|
+
.finally(() => span.end()));
|
|
122
141
|
}
|
|
123
142
|
getKeysByPrefix(prefix) {
|
|
124
143
|
return scanPatterns(this.client, `${prefix}*`);
|
|
125
144
|
}
|
|
126
145
|
delete(key) {
|
|
127
|
-
return this.
|
|
146
|
+
return this.tracer.startActiveSpan('hive.cache.delete', span => this.client
|
|
147
|
+
.del(key)
|
|
148
|
+
.then(value => value > 0, () => false)
|
|
149
|
+
.finally(() => span.end()));
|
|
128
150
|
}
|
|
129
151
|
}
|
|
130
152
|
function scanPatterns(redis, pattern, cursor = '0', keys = []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-mesh/cache-redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.105.0-alpha-20251010093057-4eeae9412ec39cfbab6e001ecaf527cf94f4ea31",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"graphql": "*"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"@graphql-mesh/cross-helpers": "^0.4.10",
|
|
10
10
|
"@graphql-mesh/string-interpolation": "0.5.9",
|
|
11
11
|
"@graphql-mesh/types": "^0.104.13",
|
|
12
|
+
"@opentelemetry/api": "^1.9.0",
|
|
12
13
|
"@whatwg-node/disposablestack": "^0.0.6",
|
|
13
14
|
"ioredis": "^5.3.2",
|
|
14
15
|
"ioredis-mock": "^8.8.3",
|
package/typings/index.d.cts
CHANGED
|
@@ -2,6 +2,7 @@ import { type HivePubSub, type KeyValueCache, type KeyValueCacheSetOptions, type
|
|
|
2
2
|
import { DisposableSymbols } from '@whatwg-node/disposablestack';
|
|
3
3
|
export default class RedisCache<V = string> implements KeyValueCache<V>, Disposable {
|
|
4
4
|
private client;
|
|
5
|
+
private tracer;
|
|
5
6
|
constructor(options: YamlConfig.Cache['redis'] & {
|
|
6
7
|
pubsub?: MeshPubSub | HivePubSub;
|
|
7
8
|
logger: Logger;
|
package/typings/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type HivePubSub, type KeyValueCache, type KeyValueCacheSetOptions, type
|
|
|
2
2
|
import { DisposableSymbols } from '@whatwg-node/disposablestack';
|
|
3
3
|
export default class RedisCache<V = string> implements KeyValueCache<V>, Disposable {
|
|
4
4
|
private client;
|
|
5
|
+
private tracer;
|
|
5
6
|
constructor(options: YamlConfig.Cache['redis'] & {
|
|
6
7
|
pubsub?: MeshPubSub | HivePubSub;
|
|
7
8
|
logger: Logger;
|