Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(redis): remove support for legacy redis client versions#12057

Open
G0maa wants to merge 3 commits intomasterfrom
feat/deprecate-legacy-redis
Open

feat(redis): remove support for legacy redis client versions#12057
G0maa wants to merge 3 commits intomasterfrom
feat/deprecate-legacy-redis

Conversation

@G0maa
Copy link
Collaborator

@G0maa G0maa commented Feb 28, 2026

Description of change

removing support for redis v3/v4 clients.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

@qodo-free-for-open-source-projects

User description

Description of change

removing support for redis v3/v4 clients.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

PR Type

Enhancement


Description

  • Remove support for legacy Redis client versions (v3/v4)

  • Simplify codebase by eliminating version detection logic

  • Standardize on Promise-based API for all Redis operations

  • Add explicit type annotations for Redis client types


Diagram Walkthrough

flowchart LR
  A["Legacy Redis v3/v4<br/>Callback-based API"] -->|Remove| B["Modern Redis v5+<br/>Promise-based API"]
  C["Version Detection<br/>Logic"] -->|Remove| D["Simplified<br/>Codebase"]
  E["Dual API Paths<br/>in Methods"] -->|Consolidate| F["Single Promise<br/>Implementation"]
Loading

File Walkthrough

Relevant files
Enhancement
RedisQueryResultCache.ts
Eliminate legacy Redis version support and callbacks         

src/cache/RedisQueryResultCache.ts

  • Remove redisMajorVersion property and version detection methods
    (detectRedisVersion(), isRedis5OrHigher())
  • Simplify connect() method by removing Redis v4 legacy mode setup and
    version checks
  • Update disconnect() to use Promise-based quit() exclusively
  • Convert getFromCache() to async and remove callback-based API fallback
  • Simplify saveInCache() to use Promise-based set() with options object
  • Update clear() to handle both redis and ioredis clients with proper
    type casting
  • Simplify deleteKey() to use Promise-based del() with array parameter
  • Add explicit type imports for RedisClientType from redis package and
    Redis, Cluster from ioredis
+17/-131

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 28, 2026

PR Code Suggestions ✨

Latest suggestions up to 69ffb7b

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix multi-key deletion behavior

In the remove method, differentiate between node-redis and ioredis clients for
the del command, as ioredis requires keys to be spread (...identifiers) while
node-redis accepts an array.

src/cache/RedisQueryResultCache.ts [195-200]

 async remove(
     identifiers: string[],
     queryRunner?: QueryRunner,
 ): Promise<void> {
-    await this.client.del(identifiers)
+    if (this.isNodeRedisClient(this.client)) {
+        await this.client.del(identifiers)
+    } else {
+        await this.client.del(...identifiers)
+    }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where remove would fail silently for ioredis by attempting to delete a single stringified key instead of multiple keys.

High
Fix single-key deletion call

In the deleteKey method, differentiate between node-redis and ioredis clients,
passing [key] to del for node-redis and just key for ioredis to ensure correct
deletion.

src/cache/RedisQueryResultCache.ts [210-212]

 protected async deleteKey(key: string): Promise<void> {
-    await this.client.del([key])
+    if (this.isNodeRedisClient(this.client)) {
+        await this.client.del([key])
+    } else {
+        await this.client.del(key)
+    }
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where deleteKey would fail silently for ioredis by passing an array [key] instead of a string key to the del command.

High
General
Re-enable disabled unit tests

Re-enable the skipped RedisQueryResultCache test suite and update the tests to
align with the new promise-based client behavior, ensuring proper test coverage.

test/unit/cache/redis-query-result-cache.test.ts [8]

-describe.skip("RedisQueryResultCache", () => {
+describe("RedisQueryResultCache", () => {
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out that skipping the entire test suite is a significant issue that hides potential regressions, and it rightly advises re-enabling and updating the tests.

Medium
  • More

Previous suggestions

✅ Suggestions up to commit 2326fe3
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix TTL set API mismatch
Suggestion Impact:The commit added client-type conditional logic (via `isNodeRedisClient`) to call `set` with a node-redis-style options object including PX expiration, while retaining the positional "PX" arguments for ioredis; it also reused the same client-type check in `clear`.

code diff:

@@ -164,7 +163,16 @@
         const value = JSON.stringify(options)
         const duration = options.duration
 
-        await this.client.set(key, value, "PX", duration)
+        if (this.isNodeRedisClient(this.client)) {
+            await this.client.set(key, value, {
+                expiration: {
+                    type: "PX",
+                    value: duration,
+                },
+            })
+        } else {
+            await this.client.set(key, value, "PX", duration)
+        }
     }
 
     /**
@@ -172,12 +180,10 @@
      * @param queryRunner
      */
     async clear(queryRunner?: QueryRunner): Promise<void> {
-        if (this.clientType === "redis") {
-            const client = this.client as RedisClientType
-            await client.flushDb()
+        if (this.isNodeRedisClient(this.client)) {
+            await this.client.flushDb()
         } else {
-            const client = this.client as Redis | Cluster
-            await client.flushdb()
+            await this.client.flushdb()
         }
     }
 
@@ -190,11 +196,7 @@
         identifiers: string[],
         queryRunner?: QueryRunner,
     ): Promise<void> {
-        await Promise.all(
-            identifiers.map((identifier) => {
-                return this.deleteKey(identifier)
-            }),
-        )
+        await this.client.del(identifiers)
     }
 
     // -------------------------------------------------------------------------
@@ -225,5 +227,11 @@
             )
         }
     }
+
+    private isNodeRedisClient(
+        client: Redis | Cluster | RedisClientType,
+    ): client is RedisClientType {
+        return this.clientType === "redis"
+    }

Use conditional logic based on the client type to call the set method with the
correct signature for redis (options object) and ioredis (positional arguments)
clients.

src/cache/RedisQueryResultCache.ts [167]

-await this.client.set(key, value, "PX", duration)
+if (this.clientType === "redis") {
+    const client = this.client as RedisClientType
+    await client.set(key, value, { PX: duration })
+} else {
+    const client = this.client as Redis | Cluster
+    await client.set(key, value, "PX", duration)
+}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where the set method's signature differs between redis and ioredis, which would cause caching to fail when using the redis client.

High
Fix delete API incompatibility

Use conditional logic based on the client type to call the del method with the
correct signature for redis (array of keys) and ioredis (string key) clients.

src/cache/RedisQueryResultCache.ts [209]

-await this.client.del([key])
+if (this.clientType === "redis") {
+    const client = this.client as RedisClientType
+    await client.del([key])
+} else {
+    const client = this.client as Redis | Cluster
+    await client.del(key)
+}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where the del method's signature differs between redis and ioredis, which would cause cache key deletion to fail when using the ioredis client.

High
General
Re-enable skipped unit tests

Re-enable the skipped RedisQueryResultCache test suite and update the tests to
align with the refactored code instead of disabling them.

test/unit/cache/redis-query-result-cache.test.ts [8]

-describe.skip("RedisQueryResultCache", () => {
+describe("RedisQueryResultCache", () => {
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that skipping the test suite is risky and hides potential regressions, which is especially important after a significant refactoring of the Redis cache logic.

Medium
✅ Suggestions up to commit a404fde
CategorySuggestion                                                                                                                                    Impact
Possible issue
Use correct arguments for set
Suggestion Impact:The commit updated the cache set logic to branch for node-redis vs ioredis, using an options object for node-redis and the "PX", duration argument form for ioredis; it also added a type-guard helper to detect node-redis.

code diff:

@@ -164,7 +163,16 @@
         const value = JSON.stringify(options)
         const duration = options.duration
 
-        await this.client.set(key, value, "PX", duration)
+        if (this.isNodeRedisClient(this.client)) {
+            await this.client.set(key, value, {
+                expiration: {
+                    type: "PX",
+                    value: duration,
+                },
+            })
+        } else {
+            await this.client.set(key, value, "PX", duration)
+        }
     }
 
     /**
@@ -172,12 +180,10 @@
      * @param queryRunner
      */
     async clear(queryRunner?: QueryRunner): Promise<void> {
-        if (this.clientType === "redis") {
-            const client = this.client as RedisClientType
-            await client.flushDb()
+        if (this.isNodeRedisClient(this.client)) {
+            await this.client.flushDb()
         } else {
-            const client = this.client as Redis | Cluster
-            await client.flushdb()
+            await this.client.flushdb()
         }
     }
 
@@ -190,11 +196,7 @@
         identifiers: string[],
         queryRunner?: QueryRunner,
     ): Promise<void> {
-        await Promise.all(
-            identifiers.map((identifier) => {
-                return this.deleteKey(identifier)
-            }),
-        )
+        await this.client.del(identifiers)
     }
 
     // -------------------------------------------------------------------------
@@ -225,5 +227,11 @@
             )
         }
     }
+
+    private isNodeRedisClient(
+        client: Redis | Cluster | RedisClientType,
+    ): client is RedisClientType {
+        return this.clientType === "redis"
+    }

Conditionally call the set method with the correct arguments based on the
clientType, as the method signature differs between the redis and ioredis
libraries.

src/cache/RedisQueryResultCache.ts [167]

-await this.client.set(key, value, "PX", duration)
+if (this.clientType === "redis") {
+    await (this.client as RedisClientType).set(key, value, {
+        PX: duration,
+    })
+} else {
+    await (this.client as Redis | Cluster).set(
+        key,
+        value,
+        "PX",
+        duration,
+    )
+}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the set method is called with arguments that are only valid for ioredis, which would cause a runtime error when using the redis client.

High
Use correct arguments for del

Use the correct arguments for the del method based on the clientType, as the
redis and ioredis libraries expect different argument formats.

src/cache/RedisQueryResultCache.ts [209]

-await this.client.del([key])
+if (this.clientType === "redis") {
+    await (this.client as RedisClientType).del([key])
+} else {
+    await (this.client as Redis | Cluster).del(key)
+}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where del([key]) is used. This is incorrect for ioredis and would attempt to delete a key with a literal name including brackets, instead of the intended key.

High
General
Convert promise chain to await
Suggestion Impact:Replaced the this.client.get(key).then(...) promise chain with an awaited call and returned the parsed result directly.

code diff:

-        return this.client.get(key).then((result: any) => {
-            return result ? JSON.parse(result) : undefined
-        })
+        const result = await this.client.get(key)
+        return result ? JSON.parse(result) : undefined

Refactor the promise-based .then() chain to use async/await for improved
readability and consistency within the async method.

src/cache/RedisQueryResultCache.ts [137-139]

-return this.client.get(key).then((result: any) => {
-    return result ? JSON.parse(result) : undefined
-})
+const result = await this.client.get(key)
+return result ? JSON.parse(result) : undefined
Suggestion importance[1-10]: 4

__

Why: The suggestion improves code readability and consistency by replacing a .then() promise chain with async/await, which aligns with the async method signature.

Low

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 28, 2026

commit: 69ffb7b

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 28, 2026

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (3) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Redis v3/v4 removal undocumented 📘 Rule violation ✓ Correctness
Description
The PR removes support for legacy Redis clients (v3/v4) but no documentation updates are included.
This can break existing users without guidance on required Redis client versions and migration
steps.
Code

src/cache/RedisQueryResultCache.ts[R59-62]

+            this.client = this.redis.createClient(clientOptions)

           if (
               typeof this.dataSource.options.cache === "object" &&
Evidence
The checklist requires docs to be updated for user-facing changes. The PR description states legacy
Redis client support is being removed, and the code changes remove legacy-version handling and now
always uses the modern connect flow, but there are no accompanying doc changes in this PR diff.

Rule 2: Docs updated for user-facing changes
src/cache/RedisQueryResultCache.ts[59-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This change removes support for legacy `redis` v3/v4 clients, but the PR does not include documentation updates to reflect the new minimum supported versions and any migration steps.
## Issue Context
Users relying on older Redis client versions may experience runtime failures or type/API mismatches after upgrading.
## Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[59-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Optional deps imported eagerly 🐞 Bug ⛯ Reliability
Description
RedisQueryResultCache now imports types from optional peer dependencies (and ioredis as a value
import), which forces TypeScript to resolve those modules during build and leaks those types into
generated .d.ts. Because DataSource imports the cache factory, this can break TypeORM
compilation/consumption for users who do not have redis/ioredis installed (even if cache is
disabled).
Code

src/cache/RedisQueryResultCache.ts[R7-8]

+import Redis, { Cluster } from "ioredis"
+import { RedisClientType } from "redis"
Evidence
RedisQueryResultCache adds top-level imports from optional packages and uses them in an exported
class property type. TypeORM emits declarations (tsconfig.json), so the compiler must resolve these
modules/types, and consumers may also need these deps just to typecheck. This violates the existing
optional-peer-dependency/dynamic-load pattern (PlatformTools.load) and is reachable via common
entrypoints because DataSource imports QueryResultCacheFactory which imports RedisQueryResultCache.

src/cache/RedisQueryResultCache.ts[1-32]
src/cache/RedisQueryResultCache.ts[212-227]
src/platform/PlatformTools.ts[106-114]
src/cache/QueryResultCacheFactory.ts[1-45]
src/data-source/DataSource.ts[31-35]
src/data-source/DataSource.ts[141-156]
package.json[109-168]
package.json[169-185]
package.json[186-222]
tsconfig.json[1-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`RedisQueryResultCache` introduces top-level imports from `ioredis` and `redis` types, which breaks TypeORM’s optional peer dependency model and can cause `tsc`/declaration generation to fail when `ioredis` isn’t installed. It also leaks those types into the exported class declaration, forcing downstream typecheckers to have those packages installed.
### Issue Context
- TypeORM uses `PlatformTools.load()` to dynamically `require()` optional dependencies.
- `DataSource` imports `QueryResultCacheFactory`, which imports `RedisQueryResultCache`, so any eager import in that file is effectively part of the common entry path.
- `tsconfig.json` enables `declaration: true`, so any referenced external types must be resolvable and may appear in `.d.ts`.
### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[1-32]
- src/cache/RedisQueryResultCache.ts[23-31]
- src/cache/RedisQueryResultCache.ts[212-227]
### Suggested approach
- Remove `import Redis, { Cluster } from &amp;quot;ioredis&amp;quot;` and `import { RedisClientType } from &amp;quot;redis&amp;quot;`.
- Change `protected client` back to `any`/`unknown`, **or** define a small local structural interface that covers only the methods TypeORM calls (`connect`, `quit`, `get`, `set`, `del`, `flushDb/flushdb`).
- Keep using `PlatformTools.load()` for runtime module loading.
- Ensure generated `.d.ts` does not reference `ioredis`/`redis` types.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stale cache unit test 🐞 Bug ✓ Correctness
Description
The unit tests still invoke the removed detectRedisVersion()/redisMajorVersion logic via (cache as
any), which will now throw at runtime and fail CI (tests run from compiled output).
Code

src/cache/RedisQueryResultCache.ts[L313-341]

-    /**
-     * Detects the Redis package version by reading the installed package.json
-     * and sets the appropriate API version (3 for callback-based, 5 for Promise-based).
-     */
-    private detectRedisVersion(): void {
-        if (this.clientType !== "redis") return
-        const version = PlatformTools.readPackageVersion("redis")
-        const major = parseInt(version.split(".")[0], 10)
-        if (isNaN(major)) {
-            throw new TypeORMError(`Invalid Redis version format: ${version}`)
-        }
-        if (major <= 4) {
-            // Redis 3/4 uses callback-based API
-            this.redisMajorVersion = 3
-        } else {
-            // Redis 5+ uses Promise-based API
-            this.redisMajorVersion = 5
-        }
-    }
-
-    /**
-     * Checks if Redis version is 5.x or higher
-     */
-    private isRedis5OrHigher(): boolean {
-        if (this.clientType !== "redis") return false
-        return (
-            this.redisMajorVersion !== undefined && this.redisMajorVersion >= 5
-        )
-    }
Evidence
The test suite explicitly calls detectRedisVersion() and asserts redisMajorVersion. The updated
RedisQueryResultCache no longer defines those members (class ends after loadRedis). Since mocha runs
compiled tests, this will fail at runtime with `TypeError: cache.detectRedisVersion is not a
function`.

test/unit/cache/redis-query-result-cache.test.ts[8-106]
src/cache/RedisQueryResultCache.ts[200-228]
.mocharc.json[6-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Unit tests still rely on `detectRedisVersion()` and `redisMajorVersion`, which were removed. These tests will now fail at runtime.
### Issue Context
`RedisQueryResultCache` was simplified and no longer has version detection. The existing unit test suite is now out of sync.
### Fix Focus Areas
- test/unit/cache/redis-query-result-cache.test.ts[8-106]
- src/cache/RedisQueryResultCache.ts[1-228]
### Suggested approach
- Remove the `detectRedisVersion` describe block entirely, or rewrite it to test the new behavior.
- Add targeted tests for:
- `loadRedis()` throwing a helpful error when the selected client type isn’t installed (stubbing `PlatformTools.load` to throw).
- `connect()` path for `clientType === &amp;quot;redis&amp;quot;` calling `.connect()` on the created client.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
4. Docs missing redis v3/v4 removal 📘 Rule violation ✓ Correctness
Description
This PR removes legacy Redis v3/v4 client support, which is a user-facing behavior/API compatibility
change. The PR indicates documentation was not updated, violating the docs-update requirement.
Code

src/cache/RedisQueryResultCache.ts[R56-60]

               ...cacheOptions?.options,
           }

-            // Create initial client to test Redis version
-            let tempClient = this.redis.createClient(clientOptions)
-            const isRedis4Plus = typeof tempClient.connect === "function"
-
-            if (isRedis4Plus) {
-                // Redis 4+ detected, recreate with legacyMode for Redis 4.x
-                // (Redis 5 will ignore legacyMode if not needed)
-                clientOptions.legacyMode = true
-                tempClient = this.redis.createClient(clientOptions)
-            }
-
-            // Set as the main client
-            this.client = tempClient
+            this.client = this.redis.createClient(clientOptions)
Evidence
The compliance checklist requires documentation updates for user-facing changes. The PR description
explicitly states legacy redis client versions are being removed and the docs checklist item is left
unchecked, while the code change removes the legacy/compat handling.

Rule 2: Docs updated for user-facing changes
src/cache/RedisQueryResultCache.ts[56-60]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR removes support for legacy Redis v3/v4 clients, but documentation was not updated to reflect the new minimum supported Redis client/version and any migration notes.
## Issue Context
The PR itself indicates a user-facing change (dropping Redis v3/v4) and the documentation checklist item is unchecked.
## Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[56-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. getFromCache introduces any📘 Rule violation ✓ Correctness
Description
New code adds result: any in getFromCache, which weakens type safety and resembles the
prohibited "any-casts to bypass types" style. This violates the requirement to avoid AI-like
type-bypassing patterns.
Code

src/cache/RedisQueryResultCache.ts[R137-139]

+        return this.client.get(key).then((result: any) => {
+            return result ? JSON.parse(result) : undefined
       })
Evidence
The checklist prohibits introducing type-bypassing any usage. The added callback explicitly
annotates result as any in newly changed lines.

Rule 4: Remove AI-generated noise
src/cache/RedisQueryResultCache.ts[137-139]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getFromCache` introduces `result: any`, which bypasses type checking and violates the &amp;quot;no any-casts to bypass types&amp;quot; requirement.
## Issue Context
`this.client.get(key)` should have a known return type for the chosen redis client types (commonly `string | null`), and JSON parsing should be done with that type.
## Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[137-139]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Hard peer-dep imports 🐞 Bug ⛯ Reliability
Description
RedisQueryResultCache now has top-level (value) imports of redis and ioredis, which are optional
peer dependencies. Because DataSource imports the cache factory at module load time, apps can crash
on import (and/or TS type resolution) even when cache is not used.
Code

src/cache/RedisQueryResultCache.ts[R7-8]

+import Redis, { Cluster } from "ioredis"
+import { RedisClientType } from "redis"
Evidence
DataSource imports QueryResultCacheFactory, which imports RedisQueryResultCache at top-level,
so the new static imports are evaluated during normal TypeORM module loading. Since redis and
ioredis are optional peer dependencies, they are not guaranteed to be installed, making these
imports a startup-breaking change.

src/cache/RedisQueryResultCache.ts[1-32]
src/cache/QueryResultCacheFactory.ts[1-6]
src/data-source/DataSource.ts[24-35]
package.json[169-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`RedisQueryResultCache` now statically imports `redis` and `ioredis`. Since these are optional *peer* dependencies, this can break consumers at runtime/compile-time even if they never enable cache.
### Issue Context
`DataSource` imports `QueryResultCacheFactory` which imports `RedisQueryResultCache` at module scope, so the static imports execute during normal TypeORM initialization.
### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[1-32]
- src/cache/RedisQueryResultCache.ts[23-32]
### What to change
- Remove value imports from `redis` and `ioredis`.
- Avoid referencing peer-dep types in exported declarations (e.g., keep `protected client: any` or a local structural interface).
- Keep runtime loading via `PlatformTools.load(...)` only, preserving optional peer dependency behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Wrong SET signature🐞 Bug ✓ Correctness
Description
storeInCache uses a legacy positional SET signature ("PX", duration) regardless of client
type. With redis peer dependency pinned to v5, this can throw or mis-set TTL for node-redis
clients.
Code

src/cache/RedisQueryResultCache.ts[167]

+        await this.client.set(key, value, "PX", duration)
Evidence
The implementation unconditionally calls this.client.set(key, value, "PX", duration) while
elsewhere (clear) it already acknowledges redis-vs-ioredis API differences. Given the repo
declares redis v5 as the peer dependency, this call shape is very likely incompatible with the
intended supported client API.

src/cache/RedisQueryResultCache.ts[156-168]
src/cache/RedisQueryResultCache.ts[174-181]
package.json[169-185]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`storeInCache()` calls `SET` using a legacy positional signature (`&amp;quot;PX&amp;quot;, duration`) for all client types, which is risky/incompatible with the supported node-redis v5 client.
### Issue Context
The class already branches per client type in `clear()` (e.g., `flushDb` vs `flushdb`), implying command APIs differ.
### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[156-168]
### What to change
- Update `storeInCache()` to branch on `this.clientType`.
- For `redis`: call `set(key, value, { PX: duration })` (or equivalent modern options object).
- For `ioredis` / `ioredis/cluster`: keep `set(key, value, &amp;quot;PX&amp;quot;, duration)` (or the ioredis-supported alternative).
- Add/adjust unit tests to assert the correct signature is used for each client type.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Wrong DEL arguments 🐞 Bug ✓ Correctness
Description
deleteKey calls del([key]), passing an array instead of a key string/variadic keys. This is
likely incompatible with the node-redis v5 API and can cause runtime errors when removing cache
keys.
Code

src/cache/RedisQueryResultCache.ts[209]

+        await this.client.del([key])
Evidence
deleteKey unconditionally passes an array to del. Given the supported redis peer dependency is
v5, this call shape is risky and should be aligned with the expected client API (and/or branched by
clientType, similar to clear).

src/cache/RedisQueryResultCache.ts[204-210]
package.json[169-185]
src/cache/RedisQueryResultCache.ts[174-181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`deleteKey()` currently calls `del([key])`. Passing an array is likely not compatible with the supported node-redis v5 API.
### Issue Context
The class already branches per client type for other commands (e.g., flushDb/flushdb).
### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[208-210]
- src/cache/RedisQueryResultCache.ts[189-198]
### What to change
- Update `deleteKey(key)` to call `del(key)` (or the correct per-client signature).
- (Optional) Update `remove(identifiers)` to delete multiple keys in a single `del(...)` call when supported, instead of looping `deleteKey`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

9. Disconnect doesn't clear client 🐞 Bug ⛯ Reliability
Description
disconnect() no longer clears the stored client reference after quit(), increasing risk of
accidental use-after-disconnect and making lifecycle management less robust.
Code

src/cache/RedisQueryResultCache.ts[R114-115]

+        await this.client.quit()
   }
Evidence
The current disconnect implementation only calls quit(). There is no state reset, so subsequent
operations can still access a stale client reference.

src/cache/RedisQueryResultCache.ts[110-115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`disconnect()` does not clear `this.client` after quitting, leaving a stale reference and making use-after-disconnect more likely.
### Issue Context
Previous implementation cleared the client reference; the new simplified version does not.
### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[110-115]
- src/cache/RedisQueryResultCache.ts[23-27]
### Suggested approach
- Change `protected client` to allow `undefined` and set `this.client = undefined` after `await this.client.quit()`.
- Optionally add guards in public methods (get/store/clear/remove) to throw a clear error if `client` is undefined.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Tests skipped🐞 Bug ⛯ Reliability
Description
The RedisQueryResultCache unit tests are now skipped entirely, removing coverage for cache behavior
during a risky refactor. This increases the likelihood of shipping runtime regressions in
redis/ioredis integrations.
Code

test/unit/cache/redis-query-result-cache.test.ts[8]

+describe.skip("RedisQueryResultCache", () => {
Evidence
The test suite is explicitly disabled via describe.skip, so CI will not exercise any
RedisQueryResultCache behavior or validate the refactor.

test/unit/cache/redis-query-result-cache.test.ts[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The RedisQueryResultCache unit suite is fully skipped, and the remaining tests target a removed method (`detectRedisVersion`). This leaves the refactor untested.
### Issue Context
This PR changes command invocation patterns and dependency loading behavior; tests should validate client-specific method signatures and optional dependency behavior.
### Fix Focus Areas
- test/unit/cache/redis-query-result-cache.test.ts[1-107]
- src/cache/RedisQueryResultCache.ts[52-108]
- src/cache/RedisQueryResultCache.ts[156-168]
- src/cache/RedisQueryResultCache.ts[208-210]
### What to change
- Replace `describe.skip(...)` with `describe(...)`.
- Remove/update the `detectRedisVersion` tests.
- Add unit tests that stub the underlying client and assert:
- For `clientType: &amp;quot;redis&amp;quot;`, `storeInCache` uses the redis-v5-compatible `set` signature.
- For `clientType: &amp;quot;ioredis&amp;quot;` / `&amp;quot;ioredis/cluster&amp;quot;`, `storeInCache` uses the expected ioredis signature.
- `deleteKey` calls `del` with the correct argument shape.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

import { DataSource } from "../../../src/data-source/DataSource"

describe("RedisQueryResultCache", () => {
describe.skip("RedisQueryResultCache", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this skip?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only verifies "detectRedisVersion" which the RedisQueryResultCache doesn't have anymore.

I'm skipping before deleting to see if that's the only test that fails.

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Docs missing redis v3/v4 removal 📘 Rule violation ✓ Correctness
Description
This PR removes legacy Redis v3/v4 client support, which is a user-facing behavior/API compatibility
change. The PR indicates documentation was not updated, violating the docs-update requirement.
Code

src/cache/RedisQueryResultCache.ts[R56-60]

                ...cacheOptions?.options,
            }

-            // Create initial client to test Redis version
-            let tempClient = this.redis.createClient(clientOptions)
-            const isRedis4Plus = typeof tempClient.connect === "function"
-
-            if (isRedis4Plus) {
-                // Redis 4+ detected, recreate with legacyMode for Redis 4.x
-                // (Redis 5 will ignore legacyMode if not needed)
-                clientOptions.legacyMode = true
-                tempClient = this.redis.createClient(clientOptions)
-            }
-
-            // Set as the main client
-            this.client = tempClient
+            this.client = this.redis.createClient(clientOptions)
Evidence
The compliance checklist requires documentation updates for user-facing changes. The PR description
explicitly states legacy redis client versions are being removed and the docs checklist item is left
unchecked, while the code change removes the legacy/compat handling.

Rule 2: Docs updated for user-facing changes
src/cache/RedisQueryResultCache.ts[56-60]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR removes support for legacy Redis v3/v4 clients, but documentation was not updated to reflect the new minimum supported Redis client/version and any migration notes.

## Issue Context
The PR itself indicates a user-facing change (dropping Redis v3/v4) and the documentation checklist item is unchecked.

## Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[56-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. getFromCache introduces any 📘 Rule violation ✓ Correctness
Description
New code adds result: any in getFromCache, which weakens type safety and resembles the
prohibited "any-casts to bypass types" style. This violates the requirement to avoid AI-like
type-bypassing patterns.
Code

src/cache/RedisQueryResultCache.ts[R137-139]

+        return this.client.get(key).then((result: any) => {
+            return result ? JSON.parse(result) : undefined
        })
Evidence
The checklist prohibits introducing type-bypassing any usage. The added callback explicitly
annotates result as any in newly changed lines.

Rule 4: Remove AI-generated noise
src/cache/RedisQueryResultCache.ts[137-139]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getFromCache` introduces `result: any`, which bypasses type checking and violates the &quot;no any-casts to bypass types&quot; requirement.

## Issue Context
`this.client.get(key)` should have a known return type for the chosen redis client types (commonly `string | null`), and JSON parsing should be done with that type.

## Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[137-139]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Hard peer-dep imports 🐞 Bug ⛯ Reliability
Description
RedisQueryResultCache now has top-level (value) imports of redis and ioredis, which are optional
peer dependencies. Because DataSource imports the cache factory at module load time, apps can crash
on import (and/or TS type resolution) even when cache is not used.
Code

src/cache/RedisQueryResultCache.ts[R7-8]

+import Redis, { Cluster } from "ioredis"
+import { RedisClientType } from "redis"
Evidence
DataSource imports QueryResultCacheFactory, which imports RedisQueryResultCache at top-level,
so the new static imports are evaluated during normal TypeORM module loading. Since redis and
ioredis are optional peer dependencies, they are not guaranteed to be installed, making these
imports a startup-breaking change.

src/cache/RedisQueryResultCache.ts[1-32]
src/cache/QueryResultCacheFactory.ts[1-6]
src/data-source/DataSource.ts[24-35]
package.json[169-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RedisQueryResultCache` now statically imports `redis` and `ioredis`. Since these are optional *peer* dependencies, this can break consumers at runtime/compile-time even if they never enable cache.

### Issue Context
`DataSource` imports `QueryResultCacheFactory` which imports `RedisQueryResultCache` at module scope, so the static imports execute during normal TypeORM initialization.

### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[1-32]
- src/cache/RedisQueryResultCache.ts[23-32]

### What to change
- Remove value imports from `redis` and `ioredis`.
- Avoid referencing peer-dep types in exported declarations (e.g., keep `protected client: any` or a local structural interface).
- Keep runtime loading via `PlatformTools.load(...)` only, preserving optional peer dependency behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Wrong SET signature 🐞 Bug ✓ Correctness
Description
storeInCache uses a legacy positional SET signature ("PX", duration) regardless of client
type. With redis peer dependency pinned to v5, this can throw or mis-set TTL for node-redis
clients.
Code

src/cache/RedisQueryResultCache.ts[167]

+        await this.client.set(key, value, "PX", duration)
Evidence
The implementation unconditionally calls this.client.set(key, value, "PX", duration) while
elsewhere (clear) it already acknowledges redis-vs-ioredis API differences. Given the repo
declares redis v5 as the peer dependency, this call shape is very likely incompatible with the
intended supported client API.

src/cache/RedisQueryResultCache.ts[156-168]
src/cache/RedisQueryResultCache.ts[174-181]
package.json[169-185]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`storeInCache()` calls `SET` using a legacy positional signature (`&quot;PX&quot;, duration`) for all client types, which is risky/incompatible with the supported node-redis v5 client.

### Issue Context
The class already branches per client type in `clear()` (e.g., `flushDb` vs `flushdb`), implying command APIs differ.

### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[156-168]

### What to change
- Update `storeInCache()` to branch on `this.clientType`.
 - For `redis`: call `set(key, value, { PX: duration })` (or equivalent modern options object).
 - For `ioredis` / `ioredis/cluster`: keep `set(key, value, &quot;PX&quot;, duration)` (or the ioredis-supported alternative).
- Add/adjust unit tests to assert the correct signature is used for each client type.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Wrong DEL arguments 🐞 Bug ✓ Correctness
Description
deleteKey calls del([key]), passing an array instead of a key string/variadic keys. This is
likely incompatible with the node-redis v5 API and can cause runtime errors when removing cache
keys.
Code

src/cache/RedisQueryResultCache.ts[209]

+        await this.client.del([key])
Evidence
deleteKey unconditionally passes an array to del. Given the supported redis peer dependency is
v5, this call shape is risky and should be aligned with the expected client API (and/or branched by
clientType, similar to clear).

src/cache/RedisQueryResultCache.ts[204-210]
package.json[169-185]
src/cache/RedisQueryResultCache.ts[174-181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`deleteKey()` currently calls `del([key])`. Passing an array is likely not compatible with the supported node-redis v5 API.

### Issue Context
The class already branches per client type for other commands (e.g., flushDb/flushdb).

### Fix Focus Areas
- src/cache/RedisQueryResultCache.ts[208-210]
- src/cache/RedisQueryResultCache.ts[189-198]

### What to change
- Update `deleteKey(key)` to call `del(key)` (or the correct per-client signature).
- (Optional) Update `remove(identifiers)` to delete multiple keys in a single `del(...)` call when supported, instead of looping `deleteKey`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. Tests skipped 🐞 Bug ⛯ Reliability
Description
The RedisQueryResultCache unit tests are now skipped entirely, removing coverage for cache behavior
during a risky refactor. This increases the likelihood of shipping runtime regressions in
redis/ioredis integrations.
Code

test/unit/cache/redis-query-result-cache.test.ts[8]

+describe.skip("RedisQueryResultCache", () => {
Evidence
The test suite is explicitly disabled via describe.skip, so CI will not exercise any
RedisQueryResultCache behavior or validate the refactor.

test/unit/cache/redis-query-result-cache.test.ts[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The RedisQueryResultCache unit suite is fully skipped, and the remaining tests target a removed method (`detectRedisVersion`). This leaves the refactor untested.

### Issue Context
This PR changes command invocation patterns and dependency loading behavior; tests should validate client-specific method signatures and optional dependency behavior.

### Fix Focus Areas
- test/unit/cache/redis-query-result-cache.test.ts[1-107]
- src/cache/RedisQueryResultCache.ts[52-108]
- src/cache/RedisQueryResultCache.ts[156-168]
- src/cache/RedisQueryResultCache.ts[208-210]

### What to change
- Replace `describe.skip(...)` with `describe(...)`.
- Remove/update the `detectRedisVersion` tests.
- Add unit tests that stub the underlying client and assert:
 - For `clientType: &quot;redis&quot;`, `storeInCache` uses the redis-v5-compatible `set` signature.
 - For `clientType: &quot;ioredis&quot;` / `&quot;ioredis/cluster&quot;`, `storeInCache` uses the expected ioredis signature.
 - `deleteKey` calls `del` with the correct argument shape.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@G0maa
Copy link
Collaborator Author

G0maa commented Feb 28, 2026

(will fix Qodo comments)

@G0maa
Copy link
Collaborator Author

G0maa commented Feb 28, 2026

maybe I should add tests too, since it's likely storeInCache should have failed 😓

@coveralls
Copy link

coveralls commented Feb 28, 2026

Coverage Status

coverage: 81.455% (+0.04%) from 81.419%
when pulling 69ffb7b on feat/deprecate-legacy-redis
into f47246c on master.

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

* Connected redis client.
*/
protected client: any
protected client: RedisClientType | Redis | Cluster
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok to import types?

Copy link
Collaborator

@alumni alumni Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #12044 I made it mandatory to use type imports if only the types are used. Needs to be checked if importing anything from redis/ioredis is okay.

import { DataSource } from "../data-source/DataSource"
import { QueryRunner } from "../query-runner/QueryRunner"
import { TypeORMError } from "../error/TypeORMError"
import Redis, { Cluster } from "ioredis"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will made redis/ioredis a mandatory dependency of typeorm which we do not want. Type imports might work, but needs to be checked if they don't cause problems in projects using typeorm but not using redis.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually checked, since I'm doing something similar with mongo:

import type { ObjectId } from "mongodb";

would cause type errors in projects using TypeORM.

Unless: they set skipLibCheck: true in their TSConfig file. We do this in our project because we use @tsconfig/nodeXX which sets it by default, but it's not something we should force everyone to use.

this.client = undefined
})
})
await this.client.quit()
Copy link
Collaborator

@alumni alumni Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proper cleanup:

Suggested change
await this.client.quit()
const client = this.client
this.client = undefined
await client.quit()

@alumni alumni mentioned this pull request Mar 2, 2026
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants