From 5816afec610c567df65042817ddcbb46e5682ddb Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Tue, 7 Oct 2025 17:12:28 +0200 Subject: [PATCH 1/8] feat(cache-redis): add OTEL traces --- packages/cache/redis/package.json | 1 + packages/cache/redis/src/index.ts | 227 ++++++++++++++++-------------- yarn.lock | 46 +++++- 3 files changed, 168 insertions(+), 106 deletions(-) diff --git a/packages/cache/redis/package.json b/packages/cache/redis/package.json index 2b889811a97da..7ffae5ad3d3b0 100644 --- a/packages/cache/redis/package.json +++ b/packages/cache/redis/package.json @@ -38,6 +38,7 @@ "@graphql-mesh/cross-helpers": "^0.4.10", "@graphql-mesh/string-interpolation": "0.5.9", "@graphql-mesh/types": "^0.104.14", + "@opentelemetry/api": "^1.9.0", "@whatwg-node/disposablestack": "^0.0.6", "ioredis": "^5.3.2", "ioredis-mock": "^8.13.1", diff --git a/packages/cache/redis/src/index.ts b/packages/cache/redis/src/index.ts index 7d41c9ad48459..b4985bd4a12e9 100644 --- a/packages/cache/redis/src/index.ts +++ b/packages/cache/redis/src/index.ts @@ -11,6 +11,7 @@ import { type MeshPubSub, type YamlConfig, } from '@graphql-mesh/types'; +import { trace, type Tracer } from '@opentelemetry/api'; import { DisposableSymbols } from '@whatwg-node/disposablestack'; function interpolateStrWithEnv(str: string): string { @@ -19,113 +20,120 @@ function interpolateStrWithEnv(str: string): string { export default class RedisCache implements KeyValueCache, Disposable { private client: Redis | Cluster; + private tracer: Tracer; constructor( options: YamlConfig.Cache['redis'] & { pubsub?: MeshPubSub | HivePubSub; logger: Logger }, ) { - const lazyConnect = options.lazyConnect !== false; - if ('startupNodes' in options) { - const parsedUsername = - interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; - const parsedPassword = - interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; - const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; - const numDb = parseInt(parsedDb); - this.client = new Redis.Cluster( - options.startupNodes.map(s => ({ - host: s.host && interpolateStrWithEnv(s.host), - port: s.port && parseInt(interpolateStrWithEnv(s.port)), - family: s.family && parseInt(interpolateStrWithEnv(s.family)), - })), - { - dnsLookup: options.dnsLookupAsIs - ? (address, callback) => callback(null, address) - : undefined, - redisOptions: { - username: parsedUsername, - password: parsedPassword, - db: isNaN(numDb) ? undefined : numDb, + this.tracer = trace.getTracer('hive.cache.redis'); + this.tracer.startActiveSpan('hive.cache.redis.init', span => { + const lazyConnect = options.lazyConnect !== false; + if ('startupNodes' in options) { + const parsedUsername = + interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; + const parsedPassword = + interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; + const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; + const numDb = parseInt(parsedDb); + this.client = new Redis.Cluster( + options.startupNodes.map(s => ({ + host: s.host && interpolateStrWithEnv(s.host), + port: s.port && parseInt(interpolateStrWithEnv(s.port)), + family: s.family && parseInt(interpolateStrWithEnv(s.family)), + })), + { + dnsLookup: options.dnsLookupAsIs + ? (address, callback) => callback(null, address) + : undefined, + redisOptions: { + username: parsedUsername, + password: parsedPassword, + db: isNaN(numDb) ? undefined : numDb, + enableAutoPipelining: true, + ...(lazyConnect ? { lazyConnect: true } : {}), + tls: options.tls ? {} : undefined, + }, enableAutoPipelining: true, + enableOfflineQueue: true, ...(lazyConnect ? { lazyConnect: true } : {}), - tls: options.tls ? {} : undefined, }, + ); + } else if ('sentinels' in options) { + this.client = new Redis({ + name: options.name, + sentinelPassword: + options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword), + sentinels: options.sentinels.map(s => ({ + host: s.host && interpolateStrWithEnv(s.host), + port: s.port && parseInt(interpolateStrWithEnv(s.port)), + family: s.family && parseInt(interpolateStrWithEnv(s.family)), + })), + role: options.role, + enableTLSForSentinelMode: options.enableTLSForSentinelMode, enableAutoPipelining: true, enableOfflineQueue: true, - ...(lazyConnect ? { lazyConnect: true } : {}), - }, - ); - } else if ('sentinels' in options) { - this.client = new Redis({ - name: options.name, - sentinelPassword: - options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword), - sentinels: options.sentinels.map(s => ({ - host: s.host && interpolateStrWithEnv(s.host), - port: s.port && parseInt(interpolateStrWithEnv(s.port)), - family: s.family && parseInt(interpolateStrWithEnv(s.family)), - })), - role: options.role, - enableTLSForSentinelMode: options.enableTLSForSentinelMode, - enableAutoPipelining: true, - enableOfflineQueue: true, - lazyConnect, - }); - } else if (options.url) { - const redisUrl = new URL(interpolateStrWithEnv(options.url)); + lazyConnect, + }); + } else if (options.url) { + const redisUrl = new URL(interpolateStrWithEnv(options.url)); - if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) { - throw new Error('Redis URL must use either redis:// or rediss://'); - } + if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) { + throw new Error('Redis URL must use either redis:// or rediss://'); + } - if (lazyConnect) { - redisUrl.searchParams.set('lazyConnect', 'true'); - } + if (lazyConnect) { + redisUrl.searchParams.set('lazyConnect', 'true'); + } - redisUrl.searchParams.set('enableAutoPipelining', 'true'); - redisUrl.searchParams.set('enableOfflineQueue', 'true'); - 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; - if (IPV6_REGEX.test(redisUrl.hostname)) { - redisUrl.searchParams.set('family', '6'); - } - const urlStr = redisUrl.toString(); - options.logger.debug(`Connecting to Redis at ${urlStr}`); - this.client = new Redis(urlStr); - } else { - const parsedHost = interpolateStrWithEnv(options.host?.toString()) || process.env.REDIS_HOST; - const parsedPort = interpolateStrWithEnv(options.port?.toString()) || process.env.REDIS_PORT; - const parsedUsername = - interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; - const parsedPassword = - interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; - const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; - const parsedFamily = - interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY; - const numPort = parseInt(parsedPort); - const numDb = parseInt(parsedDb); - if (parsedHost) { - options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`); - this.client = new Redis({ - host: parsedHost, - port: isNaN(numPort) ? undefined : numPort, - username: parsedUsername, - password: parsedPassword, - db: isNaN(numDb) ? undefined : numDb, - family: parsedFamily === '6' ? 6 : undefined, - ...(lazyConnect ? { lazyConnect: true } : {}), - enableAutoPipelining: true, - enableOfflineQueue: true, - }); + redisUrl.searchParams.set('enableAutoPipelining', 'true'); + redisUrl.searchParams.set('enableOfflineQueue', 'true'); + 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; + if (IPV6_REGEX.test(redisUrl.hostname)) { + redisUrl.searchParams.set('family', '6'); + } + const urlStr = redisUrl.toString(); + options.logger.debug(`Connecting to Redis at ${urlStr}`); + this.client = new Redis(urlStr); } else { - options.logger.debug(`Connecting to Redis mock`); - this.client = new RedisMock(); + const parsedHost = + interpolateStrWithEnv(options.host?.toString()) || process.env.REDIS_HOST; + const parsedPort = + interpolateStrWithEnv(options.port?.toString()) || process.env.REDIS_PORT; + const parsedUsername = + interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; + const parsedPassword = + interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; + const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; + const parsedFamily = + interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY; + const numPort = parseInt(parsedPort); + const numDb = parseInt(parsedDb); + if (parsedHost) { + options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`); + this.client = new Redis({ + host: parsedHost, + port: isNaN(numPort) ? undefined : numPort, + username: parsedUsername, + password: parsedPassword, + db: isNaN(numDb) ? undefined : numDb, + family: parsedFamily === '6' ? 6 : undefined, + ...(lazyConnect ? { lazyConnect: true } : {}), + enableAutoPipelining: true, + enableOfflineQueue: true, + }); + } else { + options.logger.debug(`Connecting to Redis mock`); + this.client = new RedisMock(); + } } - } - const pubsub = toMeshPubSub(options.pubsub); - // TODO: PubSub.destroy will no longer be needed after v0 - const id = pubsub?.subscribe('destroy', () => { - this.client.disconnect(false); - pubsub.unsubscribe(id); + const pubsub = toMeshPubSub(options.pubsub); + // TODO: PubSub.destroy will no longer be needed after v0 + const id = pubsub?.subscribe('destroy', () => { + this.client.disconnect(false); + pubsub.unsubscribe(id); + }); + span.end(); }); } @@ -134,16 +142,24 @@ export default class RedisCache implements KeyValueCache, Disposa } set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise { - const stringifiedValue = JSON.stringify(value); - if (options?.ttl && options.ttl > 0) { - return this.client.set(key, stringifiedValue, 'PX', options.ttl * 1000); - } else { - return this.client.set(key, stringifiedValue); - } + return this.tracer.startActiveSpan('hive.cache.set', async span => { + const stringifiedValue = JSON.stringify(value); + if (options?.ttl && options.ttl > 0) { + return this.client + .set(key, stringifiedValue, 'PX', options.ttl * 1000) + .finally(() => span.end()); + } else { + return this.client.set(key, stringifiedValue).finally(() => span.end()); + } + }); } get(key: string): Promise { - return this.client.get(key).then(value => (value != null ? JSON.parse(value) : undefined)); + return this.tracer.startActiveSpan('hive.cache.get', span => + this.client + .get(key) + .then(value => (value != null ? JSON.parse(value) : undefined).finally(() => span.end())), + ); } getKeysByPrefix(prefix: string): Promise { @@ -151,9 +167,14 @@ export default class RedisCache implements KeyValueCache, Disposa } delete(key: string): Promise { - return this.client.del(key).then( - value => value > 0, - () => false, + return this.tracer.startActiveSpan('hive.cache.delete', span => + this.client + .del(key) + .then( + value => value > 0, + () => false, + ) + .finally(() => span.end()), ); } } diff --git a/yarn.lock b/yarn.lock index 2f0bc6ecef9b0..f39a4cdd95a92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1724,6 +1724,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" + dependencies: + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": version: 7.27.3 resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" @@ -1936,6 +1949,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/parser@npm:7.28.4" + dependencies: + "@babel/types": "npm:^7.28.4" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": version: 7.28.5 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" @@ -3219,7 +3243,22 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.7.2": +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.7.2": + version: 7.28.4 + resolution: "@babel/traverse@npm:7.28.4" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.3" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.4" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.4" + debug: "npm:^4.3.1" + checksum: 10c0/ee678fdd49c9f54a32e07e8455242390d43ce44887cea6567b233fe13907b89240c377e7633478a32c6cf1be0e17c2f7f3b0c59f0666e39c5074cc47b968489c + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.28.5": version: 7.28.5 resolution: "@babel/traverse@npm:7.28.5" dependencies: @@ -6865,6 +6904,7 @@ __metadata: "@graphql-mesh/cross-helpers": "npm:^0.4.10" "@graphql-mesh/string-interpolation": "npm:0.5.9" "@graphql-mesh/types": "npm:^0.104.14" + "@opentelemetry/api": "npm:^1.9.0" "@types/ioredis-mock": "npm:8.2.6" "@whatwg-node/disposablestack": "npm:^0.0.6" graphql: "npm:16.11.0" @@ -9139,7 +9179,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:10.10.0, @graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.3, @graphql-tools/utils@npm:^10.10.0, @graphql-tools/utils@npm:^10.3.2, @graphql-tools/utils@npm:^10.5.1, @graphql-tools/utils@npm:^10.5.4, @graphql-tools/utils@npm:^10.6.1, @graphql-tools/utils@npm:^10.6.2, @graphql-tools/utils@npm:^10.6.4, @graphql-tools/utils@npm:^10.8.0, @graphql-tools/utils@npm:^10.8.1, @graphql-tools/utils@npm:^10.9.1": +"@graphql-tools/utils@npm:10.10.0, @graphql-tools/utils@npm:^10.10.0": version: 10.10.0 resolution: "@graphql-tools/utils@npm:10.10.0" dependencies: @@ -9154,7 +9194,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:10.9.1": +"@graphql-tools/utils@npm:10.9.1, @graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.3, @graphql-tools/utils@npm:^10.3.2, @graphql-tools/utils@npm:^10.5.1, @graphql-tools/utils@npm:^10.5.4, @graphql-tools/utils@npm:^10.6.1, @graphql-tools/utils@npm:^10.6.2, @graphql-tools/utils@npm:^10.6.4, @graphql-tools/utils@npm:^10.8.0, @graphql-tools/utils@npm:^10.8.1, @graphql-tools/utils@npm:^10.9.1": version: 10.9.1 resolution: "@graphql-tools/utils@npm:10.9.1" dependencies: From 09ecbf0293ac09f4d75da74b9b8de7075bf63c9b Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Tue, 7 Oct 2025 17:13:40 +0200 Subject: [PATCH 2/8] changeset --- .changeset/shiny-paths-flow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shiny-paths-flow.md diff --git a/.changeset/shiny-paths-flow.md b/.changeset/shiny-paths-flow.md new file mode 100644 index 0000000000000..294b86b3c09c7 --- /dev/null +++ b/.changeset/shiny-paths-flow.md @@ -0,0 +1,5 @@ +--- +'@graphql-mesh/cache-redis': minor +--- + +Add support of OTEL tracing with spans for Get, Set, Delete and initialisation. From 973e357c8b329e21d735fdf433abd34941403df5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Oct 2025 15:14:57 +0000 Subject: [PATCH 3/8] chore(dependencies): updated changesets for modified dependencies --- .changeset/@graphql-mesh_cache-redis-8830-dependencies.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/@graphql-mesh_cache-redis-8830-dependencies.md diff --git a/.changeset/@graphql-mesh_cache-redis-8830-dependencies.md b/.changeset/@graphql-mesh_cache-redis-8830-dependencies.md new file mode 100644 index 0000000000000..9944ef0e3e99b --- /dev/null +++ b/.changeset/@graphql-mesh_cache-redis-8830-dependencies.md @@ -0,0 +1,5 @@ +--- +"@graphql-mesh/cache-redis": patch +--- +dependencies updates: + - Added dependency [`@opentelemetry/api@^1.9.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/api/v/1.9.0) (to `dependencies`) From 9e41519d8108df086da6d60b6b6e9461e2378cb4 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Wed, 8 Oct 2025 11:37:29 +0200 Subject: [PATCH 4/8] fix --- packages/cache/redis/src/index.ts | 198 +++++++++++++++--------------- 1 file changed, 101 insertions(+), 97 deletions(-) diff --git a/packages/cache/redis/src/index.ts b/packages/cache/redis/src/index.ts index b4985bd4a12e9..4dc4789ee5434 100644 --- a/packages/cache/redis/src/index.ts +++ b/packages/cache/redis/src/index.ts @@ -27,113 +27,116 @@ export default class RedisCache implements KeyValueCache, Disposa ) { this.tracer = trace.getTracer('hive.cache.redis'); this.tracer.startActiveSpan('hive.cache.redis.init', span => { - const lazyConnect = options.lazyConnect !== false; - if ('startupNodes' in options) { - const parsedUsername = - interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; - const parsedPassword = - interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; - const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; - const numDb = parseInt(parsedDb); - this.client = new Redis.Cluster( - options.startupNodes.map(s => ({ - host: s.host && interpolateStrWithEnv(s.host), - port: s.port && parseInt(interpolateStrWithEnv(s.port)), - family: s.family && parseInt(interpolateStrWithEnv(s.family)), - })), - { - dnsLookup: options.dnsLookupAsIs - ? (address, callback) => callback(null, address) - : undefined, - redisOptions: { - username: parsedUsername, - password: parsedPassword, - db: isNaN(numDb) ? undefined : numDb, + try { + const lazyConnect = options.lazyConnect !== false; + if ('startupNodes' in options) { + const parsedUsername = + interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; + const parsedPassword = + interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; + const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; + const numDb = parseInt(parsedDb); + this.client = new Redis.Cluster( + options.startupNodes.map(s => ({ + host: s.host && interpolateStrWithEnv(s.host), + port: s.port && parseInt(interpolateStrWithEnv(s.port)), + family: s.family && parseInt(interpolateStrWithEnv(s.family)), + })), + { + dnsLookup: options.dnsLookupAsIs + ? (address, callback) => callback(null, address) + : undefined, + redisOptions: { + username: parsedUsername, + password: parsedPassword, + db: isNaN(numDb) ? undefined : numDb, + enableAutoPipelining: true, + ...(lazyConnect ? { lazyConnect: true } : {}), + tls: options.tls ? {} : undefined, + }, enableAutoPipelining: true, + enableOfflineQueue: true, ...(lazyConnect ? { lazyConnect: true } : {}), - tls: options.tls ? {} : undefined, }, + ); + } else if ('sentinels' in options) { + this.client = new Redis({ + name: options.name, + sentinelPassword: + options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword), + sentinels: options.sentinels.map(s => ({ + host: s.host && interpolateStrWithEnv(s.host), + port: s.port && parseInt(interpolateStrWithEnv(s.port)), + family: s.family && parseInt(interpolateStrWithEnv(s.family)), + })), + role: options.role, + enableTLSForSentinelMode: options.enableTLSForSentinelMode, enableAutoPipelining: true, enableOfflineQueue: true, - ...(lazyConnect ? { lazyConnect: true } : {}), - }, - ); - } else if ('sentinels' in options) { - this.client = new Redis({ - name: options.name, - sentinelPassword: - options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword), - sentinels: options.sentinels.map(s => ({ - host: s.host && interpolateStrWithEnv(s.host), - port: s.port && parseInt(interpolateStrWithEnv(s.port)), - family: s.family && parseInt(interpolateStrWithEnv(s.family)), - })), - role: options.role, - enableTLSForSentinelMode: options.enableTLSForSentinelMode, - enableAutoPipelining: true, - enableOfflineQueue: true, - lazyConnect, - }); - } else if (options.url) { - const redisUrl = new URL(interpolateStrWithEnv(options.url)); + lazyConnect, + }); + } else if (options.url) { + const redisUrl = new URL(interpolateStrWithEnv(options.url)); - if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) { - throw new Error('Redis URL must use either redis:// or rediss://'); - } + if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) { + throw new Error('Redis URL must use either redis:// or rediss://'); + } - if (lazyConnect) { - redisUrl.searchParams.set('lazyConnect', 'true'); - } + if (lazyConnect) { + redisUrl.searchParams.set('lazyConnect', 'true'); + } - redisUrl.searchParams.set('enableAutoPipelining', 'true'); - redisUrl.searchParams.set('enableOfflineQueue', 'true'); - 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; - if (IPV6_REGEX.test(redisUrl.hostname)) { - redisUrl.searchParams.set('family', '6'); - } - const urlStr = redisUrl.toString(); - options.logger.debug(`Connecting to Redis at ${urlStr}`); - this.client = new Redis(urlStr); - } else { - const parsedHost = - interpolateStrWithEnv(options.host?.toString()) || process.env.REDIS_HOST; - const parsedPort = - interpolateStrWithEnv(options.port?.toString()) || process.env.REDIS_PORT; - const parsedUsername = - interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; - const parsedPassword = - interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; - const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; - const parsedFamily = - interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY; - const numPort = parseInt(parsedPort); - const numDb = parseInt(parsedDb); - if (parsedHost) { - options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`); - this.client = new Redis({ - host: parsedHost, - port: isNaN(numPort) ? undefined : numPort, - username: parsedUsername, - password: parsedPassword, - db: isNaN(numDb) ? undefined : numDb, - family: parsedFamily === '6' ? 6 : undefined, - ...(lazyConnect ? { lazyConnect: true } : {}), - enableAutoPipelining: true, - enableOfflineQueue: true, - }); + redisUrl.searchParams.set('enableAutoPipelining', 'true'); + redisUrl.searchParams.set('enableOfflineQueue', 'true'); + 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; + if (IPV6_REGEX.test(redisUrl.hostname)) { + redisUrl.searchParams.set('family', '6'); + } + const urlStr = redisUrl.toString(); + options.logger.debug(`Connecting to Redis at ${urlStr}`); + this.client = new Redis(urlStr); } else { - options.logger.debug(`Connecting to Redis mock`); - this.client = new RedisMock(); + const parsedHost = + interpolateStrWithEnv(options.host?.toString()) || process.env.REDIS_HOST; + const parsedPort = + interpolateStrWithEnv(options.port?.toString()) || process.env.REDIS_PORT; + const parsedUsername = + interpolateStrWithEnv(options.username?.toString()) || process.env.REDIS_USERNAME; + const parsedPassword = + interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD; + const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB; + const parsedFamily = + interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY; + const numPort = parseInt(parsedPort); + const numDb = parseInt(parsedDb); + if (parsedHost) { + options.logger.debug(`Connecting to Redis at ${parsedHost}:${parsedPort}`); + this.client = new Redis({ + host: parsedHost, + port: isNaN(numPort) ? undefined : numPort, + username: parsedUsername, + password: parsedPassword, + db: isNaN(numDb) ? undefined : numDb, + family: parsedFamily === '6' ? 6 : undefined, + ...(lazyConnect ? { lazyConnect: true } : {}), + enableAutoPipelining: true, + enableOfflineQueue: true, + }); + } else { + options.logger.debug(`Connecting to Redis mock`); + this.client = new RedisMock(); + } } + const pubsub = toMeshPubSub(options.pubsub); + // TODO: PubSub.destroy will no longer be needed after v0 + const id = pubsub?.subscribe('destroy', () => { + this.client.disconnect(false); + pubsub.unsubscribe(id); + }); + } finally { + span.end(); } - const pubsub = toMeshPubSub(options.pubsub); - // TODO: PubSub.destroy will no longer be needed after v0 - const id = pubsub?.subscribe('destroy', () => { - this.client.disconnect(false); - pubsub.unsubscribe(id); - }); - span.end(); }); } @@ -158,7 +161,8 @@ export default class RedisCache implements KeyValueCache, Disposa return this.tracer.startActiveSpan('hive.cache.get', span => this.client .get(key) - .then(value => (value != null ? JSON.parse(value) : undefined).finally(() => span.end())), + .then(value => (value != null ? JSON.parse(value) : undefined)) + .finally(() => span.end()), ); } From 1fe3a1be6a5c4897e8cc19e87db0a62ad42fd049 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 10 Oct 2025 11:23:30 +0200 Subject: [PATCH 5/8] yarn lock --- yarn.lock | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index f39a4cdd95a92..d676efb4360c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10579,6 +10579,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:30.1.2": + version: 30.1.2 + resolution: "@jest/expect-utils@npm:30.1.2" + dependencies: + "@jest/get-type": "npm:30.1.0" + checksum: 10c0/5b6c4d400ad0bd22960bd77750baf55b24bf1ebdc2cec328afe275967db76bf94f797ca4c9817cdb86bc7820b9219d3f493705f3fa94fe7720960e47805a8e1b + languageName: node + linkType: hard + "@jest/expect-utils@npm:30.2.0": version: 30.2.0 resolution: "@jest/expect-utils@npm:30.2.0" @@ -10907,6 +10916,21 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:30.0.5": + version: 30.0.5 + resolution: "@jest/types@npm:30.0.5" + dependencies: + "@jest/pattern": "npm:30.0.1" + "@jest/schemas": "npm:30.0.5" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + "@types/istanbul-reports": "npm:^3.0.4" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.33" + chalk: "npm:^4.1.2" + checksum: 10c0/fd097a390e36edacbd2c92a8378ec0cd67abec5e234bab7a80aec6eb8625568052b0c32acf472388d04c4cf384b8fa2871d0d12a56b4b06eaea93f2c6df0ec6c + languageName: node + linkType: hard + "@jest/types@npm:30.2.0": version: 30.2.0 resolution: "@jest/types@npm:30.2.0" @@ -23204,7 +23228,7 @@ __metadata: languageName: node linkType: hard -"expect@npm:30.2.0, expect@npm:^30.0.0": +"expect@npm:30.2.0": version: 30.2.0 resolution: "expect@npm:30.2.0" dependencies: @@ -23230,6 +23254,20 @@ __metadata: languageName: node linkType: hard +"expect@npm:^30.0.0": + version: 30.1.2 + resolution: "expect@npm:30.1.2" + dependencies: + "@jest/expect-utils": "npm:30.1.2" + "@jest/get-type": "npm:30.1.0" + jest-matcher-utils: "npm:30.1.2" + jest-message-util: "npm:30.1.0" + jest-mock: "npm:30.0.5" + jest-util: "npm:30.0.5" + checksum: 10c0/467c1b69549e75a1a09f3feec335e0dc968cd71370361b5d83248351cf77e705e8ddf38a4885e32a50237502ced7fcc9106462f59f33c4796462e95938b8ca19 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.2 resolution: "exponential-backoff@npm:3.1.2" @@ -27245,6 +27283,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:30.1.2": + version: 30.1.2 + resolution: "jest-diff@npm:30.1.2" + dependencies: + "@jest/diff-sequences": "npm:30.0.1" + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.0.5" + checksum: 10c0/5baba5c54d044faf77540d2b97f947ce2a735c529bdca23ccd25669085ba3912eef2a8f66f4d765e8e416b1e10b95cb1dded0ebc1633efdbef37706b4e767ecb + languageName: node + linkType: hard + "jest-diff@npm:30.2.0": version: 30.2.0 resolution: "jest-diff@npm:30.2.0" @@ -27462,6 +27512,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:30.1.2": + version: 30.1.2 + resolution: "jest-matcher-utils@npm:30.1.2" + dependencies: + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + jest-diff: "npm:30.1.2" + pretty-format: "npm:30.0.5" + checksum: 10c0/c4f81fc7d72f94b18dff807adf787d6fd081c3e150148fbbcb1559c353b27890989bcf7e10b15d763625565175bf30019e93a014078ff291646a88a9acdfc9a4 + languageName: node + linkType: hard + "jest-matcher-utils@npm:30.2.0": version: 30.2.0 resolution: "jest-matcher-utils@npm:30.2.0" @@ -27520,6 +27582,17 @@ __metadata: languageName: node linkType: hard +"jest-mock@npm:30.0.5": + version: 30.0.5 + resolution: "jest-mock@npm:30.0.5" + dependencies: + "@jest/types": "npm:30.0.5" + "@types/node": "npm:*" + jest-util: "npm:30.0.5" + checksum: 10c0/207fd79297f514a8e26ede9b4b5035e70212b8850a2f460b51d3cc58e8e7c9585bd2dbc5df2475a3321c4cd114b90e0b24190f00d6eeb88c8f088a8ed00416d5 + languageName: node + linkType: hard + "jest-mock@npm:30.2.0": version: 30.2.0 resolution: "jest-mock@npm:30.2.0" @@ -27817,6 +27890,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:30.0.5": + version: 30.0.5 + resolution: "jest-util@npm:30.0.5" + dependencies: + "@jest/types": "npm:30.0.5" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + graceful-fs: "npm:^4.2.11" + picomatch: "npm:^4.0.2" + checksum: 10c0/d3808b5f7720044d0464664c795e2b795ed82edf3b5871db74b8b603c3a0a38107668730348d26f92920ca3b8245a99cbbc2c93e77d0abb1f5e27524079a4ba8 + languageName: node + linkType: hard + "jest-util@npm:30.2.0": version: 30.2.0 resolution: "jest-util@npm:30.2.0" @@ -28034,7 +28121,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.21.7": +"jiti@npm:^1.21.6, jiti@npm:^1.21.7": version: 1.21.7 resolution: "jiti@npm:1.21.7" bin: @@ -28827,7 +28914,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^3.1.1, lilconfig@npm:^3.1.3": +"lilconfig@npm:^3.0.0, lilconfig@npm:^3.1.1, lilconfig@npm:^3.1.3": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc @@ -33114,6 +33201,24 @@ __metadata: languageName: node linkType: hard +"postcss-load-config@npm:^4.0.2": + version: 4.0.2 + resolution: "postcss-load-config@npm:4.0.2" + dependencies: + lilconfig: "npm:^3.0.0" + yaml: "npm:^2.3.4" + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: 10c0/3d7939acb3570b0e4b4740e483d6e555a3e2de815219cb8a3c8fc03f575a6bde667443aa93369c0be390af845cb84471bf623e24af833260de3a105b78d42519 + languageName: node + linkType: hard + "postcss-load-config@npm:^4.0.2 || ^5.0 || ^6.0": version: 6.0.1 resolution: "postcss-load-config@npm:6.0.1" @@ -33831,6 +33936,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.0.5": + version: 30.0.5 + resolution: "pretty-format@npm:30.0.5" + dependencies: + "@jest/schemas": "npm:30.0.5" + ansi-styles: "npm:^5.2.0" + react-is: "npm:^18.3.1" + checksum: 10c0/9f6cf1af5c3169093866c80adbfdad32f69c692b62f24ba3ca8cdec8519336123323f896396f9fa40346a41b197c5f6be15aec4d8620819f12496afaaca93f81 + languageName: node + linkType: hard + "pretty-format@npm:30.2.0, pretty-format@npm:^30.0.0": version: 30.2.0 resolution: "pretty-format@npm:30.2.0" @@ -37615,7 +37731,7 @@ __metadata: languageName: node linkType: hard -"tailwindcss@npm:3.4.18, tailwindcss@npm:^3.0.2": +"tailwindcss@npm:3.4.18": version: 3.4.18 resolution: "tailwindcss@npm:3.4.18" dependencies: @@ -37648,6 +37764,39 @@ __metadata: languageName: node linkType: hard +"tailwindcss@npm:^3.0.2": + version: 3.4.17 + resolution: "tailwindcss@npm:3.4.17" + dependencies: + "@alloc/quick-lru": "npm:^5.2.0" + arg: "npm:^5.0.2" + chokidar: "npm:^3.6.0" + didyoumean: "npm:^1.2.2" + dlv: "npm:^1.1.3" + fast-glob: "npm:^3.3.2" + glob-parent: "npm:^6.0.2" + is-glob: "npm:^4.0.3" + jiti: "npm:^1.21.6" + lilconfig: "npm:^3.1.3" + micromatch: "npm:^4.0.8" + normalize-path: "npm:^3.0.0" + object-hash: "npm:^3.0.0" + picocolors: "npm:^1.1.1" + postcss: "npm:^8.4.47" + postcss-import: "npm:^15.1.0" + postcss-js: "npm:^4.0.1" + postcss-load-config: "npm:^4.0.2" + postcss-nested: "npm:^6.2.0" + postcss-selector-parser: "npm:^6.1.2" + resolve: "npm:^1.22.8" + sucrase: "npm:^3.35.0" + bin: + tailwind: lib/cli.js + tailwindcss: lib/cli.js + checksum: 10c0/cc42c6e7fdf88a5507a0d7fea37f1b4122bec158977f8c017b2ae6828741f9e6f8cb90282c6bf2bd5951fd1220a53e0a50ca58f5c1c00eb7f5d9f8b80dc4523c + languageName: node + linkType: hard + "tapable@npm:^1.0.0": version: 1.1.3 resolution: "tapable@npm:1.1.3" @@ -40764,7 +40913,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.3.1, yaml@npm:^2.3.2, yaml@npm:^2.8.1": +"yaml@npm:^2.3.1, yaml@npm:^2.3.2, yaml@npm:^2.3.4, yaml@npm:^2.8.1": version: 2.8.1 resolution: "yaml@npm:2.8.1" bin: From 9a31085bfd3548a9d256b8e9192a486d0eba3a06 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 10 Oct 2025 11:27:15 +0200 Subject: [PATCH 6/8] handle potentially broken cache value --- packages/cache/redis/src/index.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cache/redis/src/index.ts b/packages/cache/redis/src/index.ts index 4dc4789ee5434..3f49ad588ab9e 100644 --- a/packages/cache/redis/src/index.ts +++ b/packages/cache/redis/src/index.ts @@ -146,13 +146,15 @@ export default class RedisCache implements KeyValueCache, Disposa set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise { return this.tracer.startActiveSpan('hive.cache.set', async span => { - const stringifiedValue = JSON.stringify(value); - if (options?.ttl && options.ttl > 0) { - return this.client - .set(key, stringifiedValue, 'PX', options.ttl * 1000) - .finally(() => span.end()); - } else { - return this.client.set(key, stringifiedValue).finally(() => span.end()); + try { + const stringifiedValue = JSON.stringify(value); + if (options?.ttl && options.ttl > 0) { + return await this.client.set(key, stringifiedValue, 'PX', options.ttl * 1000); + } else { + return await this.client.set(key, stringifiedValue); + } + } finally { + span.end(); } }); } From 99c6ff05c06a602bf30c0f6bb2b8a77b4c3a5304 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 31 Oct 2025 14:39:40 +0100 Subject: [PATCH 7/8] redact redis password from logs --- packages/cache/redis/src/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/cache/redis/src/index.ts b/packages/cache/redis/src/index.ts index 3f49ad588ab9e..a78503bfe0dc3 100644 --- a/packages/cache/redis/src/index.ts +++ b/packages/cache/redis/src/index.ts @@ -94,7 +94,7 @@ export default class RedisCache implements KeyValueCache, Disposa redisUrl.searchParams.set('family', '6'); } const urlStr = redisUrl.toString(); - options.logger.debug(`Connecting to Redis at ${urlStr}`); + safelyLogURL(options.logger, urlStr); this.client = new Redis(urlStr); } else { const parsedHost = @@ -199,3 +199,11 @@ function scanPatterns( return scanPatterns(redis, pattern, nextCursor, keys); }); } + +function safelyLogURL(log: Logger, url: string): void { + const logURL = new URL(url); + if (logURL.password) { + logURL.password = '*'.repeat(logURL.password.length); + } + log.debug(`Connecting to Redis at ${logURL}`); +} From bb731bdf9338fe03f467c8627692608f9604bcac Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Fri, 31 Oct 2025 18:37:17 +0100 Subject: [PATCH 8/8] fix ts errors --- packages/transports/grpc/src/index.ts | 14 +++++--------- packages/transports/odata/src/index.ts | 6 ++++-- packages/transports/rest/src/index.ts | 5 +++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/transports/grpc/src/index.ts b/packages/transports/grpc/src/index.ts index 0e9ffb3aee6cd..58e36ff5e553d 100644 --- a/packages/transports/grpc/src/index.ts +++ b/packages/transports/grpc/src/index.ts @@ -15,13 +15,7 @@ import { type Transport, } from '@graphql-mesh/transport-common'; import type { Logger } from '@graphql-mesh/types'; -import { - getDirective, - getDirectives, - getRootTypes, - mapMaybePromise, - type MaybePromise, -} from '@graphql-tools/utils'; +import { getDirective, getDirectives, getRootTypes, type MaybePromise } from '@graphql-tools/utils'; import type { ChannelCredentials } from '@grpc/grpc-js'; import { credentials, loadPackageDefinition } from '@grpc/grpc-js'; import type { ServiceClient } from '@grpc/grpc-js/build/src/make-client.js'; @@ -333,7 +327,7 @@ export class GrpcTransportHelper extends DisposableStack { } } -export default { +const transport: Transport = { getSubgraphExecutor({ transportEntry, subgraph, cwd, logger }) { const transport = new GrpcTransportHelper( transportEntry.subgraph, @@ -350,7 +344,9 @@ export default { }, ); }, -} satisfies Transport; +}; + +export default transport; function identityFn(obj: T): T { return obj; diff --git a/packages/transports/odata/src/index.ts b/packages/transports/odata/src/index.ts index d053597f46f1e..349d8db26e3e7 100644 --- a/packages/transports/odata/src/index.ts +++ b/packages/transports/odata/src/index.ts @@ -1,7 +1,7 @@ import { createDefaultExecutor, type Transport } from '@graphql-mesh/transport-common'; import { processDirectives } from '@omnigraph/odata'; -export default { +const transport: Transport = { getSubgraphExecutor({ subgraph, fetch }) { return createDefaultExecutor( processDirectives({ @@ -10,4 +10,6 @@ export default { }), ); }, -} satisfies Transport; +}; + +export default transport; diff --git a/packages/transports/rest/src/index.ts b/packages/transports/rest/src/index.ts index cb7a260f81f18..363bdbb6eaddc 100644 --- a/packages/transports/rest/src/index.ts +++ b/packages/transports/rest/src/index.ts @@ -6,7 +6,7 @@ export interface RESTTransportOptions { queryParams?: Record; } -export default { +const transport: Transport = { getSubgraphExecutor({ transportEntry, subgraph, fetch, pubsub, logger }) { const processDirectiveOpts: ProcessDirectiveArgs = { globalFetch: fetch, @@ -18,8 +18,9 @@ export default { const executor = createDefaultExecutor(processedSchema); return executor; }, -} satisfies Transport; +}; +export default transport; export { processDirectives } from './directives/process.js'; export type { ProcessDirectiveArgs } from './directives/process.js'; export { processScalarType } from './directives/scalars.js';