• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 22256862981

21 Feb 2026 12:31PM UTC coverage: 81.291% (+0.1%) from 81.176%
22256862981

push

github

web-flow
feat!: remove deprecated `Connection` and `ConnectionOptions` (#12022)

27682 of 33521 branches covered (82.58%)

Branch coverage included in aggregate %.

1205 of 1301 new or added lines in 76 files covered. (92.62%)

1 existing line in 1 file now uncovered.

93920 of 116068 relevant lines covered (80.92%)

71235.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.59
/src/cache/RedisQueryResultCache.ts
1
import { QueryResultCache } from "./QueryResultCache"
26✔
2
import { QueryResultCacheOptions } from "./QueryResultCacheOptions"
26✔
3
import { PlatformTools } from "../platform/PlatformTools"
26✔
4
import { DataSource } from "../data-source/DataSource"
26✔
5
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
6
import { TypeORMError } from "../error/TypeORMError"
26✔
7

26✔
8
/**
26✔
9
 * Caches query result into Redis database.
26✔
10
 */
26✔
11
export class RedisQueryResultCache implements QueryResultCache {
26✔
12
    // -------------------------------------------------------------------------
26✔
13
    // Protected Properties
26✔
14
    // -------------------------------------------------------------------------
26✔
15

26✔
16
    /**
26✔
17
     * Redis module instance loaded dynamically.
26✔
18
     */
26✔
19
    protected redis: any
26✔
20

26✔
21
    /**
26✔
22
     * Connected redis client.
26✔
23
     */
26✔
24
    protected client: any
26✔
25

26✔
26
    /**
26✔
27
     * Type of the Redis Client (redis or ioredis).
26✔
28
     */
26✔
29
    protected clientType: "redis" | "ioredis" | "ioredis/cluster"
26✔
30

26✔
31
    /**
26✔
32
     * Redis major version number
26✔
33
     */
26✔
34
    protected redisMajorVersion: number | undefined
26✔
35

26✔
36
    // -------------------------------------------------------------------------
26✔
37
    // Constructor
26✔
38
    // -------------------------------------------------------------------------
26✔
39

26✔
40
    constructor(
26✔
41
        protected dataSource: DataSource,
130✔
42
        clientType: "redis" | "ioredis" | "ioredis/cluster",
130✔
43
    ) {
130✔
44
        this.clientType = clientType
130✔
45
        this.redis = this.loadRedis()
130✔
46
    }
130✔
47

26✔
48
    // -------------------------------------------------------------------------
26✔
49
    // Public Methods
26✔
50
    // -------------------------------------------------------------------------
26✔
51

26✔
52
    /**
26✔
53
     * Creates a connection with given cache provider.
26✔
54
     */
26✔
55
    async connect(): Promise<void> {
26✔
NEW
56
        const cacheOptions: any = this.dataSource.options.cache
×
57
        if (this.clientType === "redis") {
×
58
            const clientOptions = {
×
59
                ...cacheOptions?.options,
×
60
            }
×
61

×
62
            // Create initial client to test Redis version
×
63
            let tempClient = this.redis.createClient(clientOptions)
×
64
            const isRedis4Plus = typeof tempClient.connect === "function"
×
65

×
66
            if (isRedis4Plus) {
×
67
                // Redis 4+ detected, recreate with legacyMode for Redis 4.x
×
68
                // (Redis 5 will ignore legacyMode if not needed)
×
69
                clientOptions.legacyMode = true
×
70
                tempClient = this.redis.createClient(clientOptions)
×
71
            }
×
72

×
73
            // Set as the main client
×
74
            this.client = tempClient
×
75

×
76
            if (
×
NEW
77
                typeof this.dataSource.options.cache === "object" &&
×
NEW
78
                this.dataSource.options.cache.ignoreErrors
×
79
            ) {
×
80
                this.client.on("error", (err: any) => {
×
NEW
81
                    this.dataSource.logger.log("warn", err)
×
82
                })
×
83
            }
×
84

×
85
            // Connect if Redis 4+
×
86
            if (typeof this.client.connect === "function") {
×
87
                await this.client.connect()
×
88
            }
×
89

×
90
            // Detect precise version after connection is established
×
91
            this.detectRedisVersion()
×
92
        } else if (this.clientType === "ioredis") {
×
93
            if (cacheOptions && cacheOptions.port) {
×
94
                if (cacheOptions.options) {
×
95
                    this.client = new this.redis(
×
96
                        cacheOptions.port,
×
97
                        cacheOptions.options,
×
98
                    )
×
99
                } else {
×
100
                    this.client = new this.redis(cacheOptions.port)
×
101
                }
×
102
            } else if (cacheOptions && cacheOptions.options) {
×
103
                this.client = new this.redis(cacheOptions.options)
×
104
            } else {
×
105
                this.client = new this.redis()
×
106
            }
×
107
        } else if (this.clientType === "ioredis/cluster") {
×
108
            if (
×
109
                cacheOptions &&
×
110
                cacheOptions.options &&
×
111
                Array.isArray(cacheOptions.options)
×
112
            ) {
×
113
                this.client = new this.redis.Cluster(cacheOptions.options)
×
114
            } else if (
×
115
                cacheOptions &&
×
116
                cacheOptions.options &&
×
117
                cacheOptions.options.startupNodes
×
118
            ) {
×
119
                this.client = new this.redis.Cluster(
×
120
                    cacheOptions.options.startupNodes,
×
121
                    cacheOptions.options.options,
×
122
                )
×
123
            } else {
×
124
                throw new TypeORMError(
×
125
                    `options.startupNodes required for ${this.clientType}.`,
×
126
                )
×
127
            }
×
128
        }
×
129
    }
×
130

26✔
131
    /**
26✔
132
     * Disconnects the connection
26✔
133
     */
26✔
134
    async disconnect(): Promise<void> {
26✔
135
        if (this.isRedis5OrHigher()) {
×
136
            // Redis 5+ uses quit() that returns a Promise
×
137
            await this.client.quit()
×
138
            this.client = undefined
×
139
            return
×
140
        }
×
141

×
142
        // Redis 3/4 callback style
×
143
        return new Promise<void>((ok, fail) => {
×
144
            this.client.quit((err: any, result: any) => {
×
145
                if (err) return fail(err)
×
146
                ok()
×
147
                this.client = undefined
×
148
            })
×
149
        })
×
150
    }
×
151

26✔
152
    /**
26✔
153
     * Creates table for storing cache if it does not exist yet.
26✔
154
     * @param queryRunner
26✔
155
     */
26✔
156
    async synchronize(queryRunner: QueryRunner): Promise<void> {}
26✔
157

26✔
158
    /**
26✔
159
     * Get data from cache.
26✔
160
     * Returns cache result if found.
26✔
161
     * Returns undefined if result is not cached.
26✔
162
     * @param options
26✔
163
     * @param queryRunner
26✔
164
     */
26✔
165
    getFromCache(
26✔
166
        options: QueryResultCacheOptions,
×
167
        queryRunner?: QueryRunner,
×
168
    ): Promise<QueryResultCacheOptions | undefined> {
×
169
        const key = options.identifier || options.query
×
170
        if (!key) return Promise.resolve(undefined)
×
171

×
172
        if (this.isRedis5OrHigher()) {
×
173
            // Redis 5+ Promise-based API
×
174
            return this.client.get(key).then((result: any) => {
×
175
                return result ? JSON.parse(result) : undefined
×
176
            })
×
177
        }
×
178

×
179
        // Redis 3/4 callback-based API
×
180
        return new Promise<QueryResultCacheOptions | undefined>((ok, fail) => {
×
181
            this.client.get(key, (err: any, result: any) => {
×
182
                if (err) return fail(err)
×
183
                ok(result ? JSON.parse(result) : undefined)
×
184
            })
×
185
        })
×
186
    }
×
187

26✔
188
    /**
26✔
189
     * Checks if cache is expired or not.
26✔
190
     * @param savedCache
26✔
191
     */
26✔
192
    isExpired(savedCache: QueryResultCacheOptions): boolean {
26✔
193
        return savedCache.time! + savedCache.duration < Date.now()
×
194
    }
×
195

26✔
196
    /**
26✔
197
     * Stores given query result in the cache.
26✔
198
     * @param options
26✔
199
     * @param savedCache
26✔
200
     * @param queryRunner
26✔
201
     */
26✔
202
    async storeInCache(
26✔
203
        options: QueryResultCacheOptions,
×
204
        savedCache: QueryResultCacheOptions,
×
205
        queryRunner?: QueryRunner,
×
206
    ): Promise<void> {
×
207
        const key = options.identifier || options.query
×
208
        if (!key) return
×
209

×
210
        const value = JSON.stringify(options)
×
211
        const duration = options.duration
×
212

×
213
        if (this.isRedis5OrHigher()) {
×
214
            // Redis 5+ Promise-based API with PX option
×
215
            await this.client.set(key, value, {
×
216
                PX: duration,
×
217
            })
×
218
            return
×
219
        }
×
220

×
221
        // Redis 3/4 callback-based API
×
222
        return new Promise<void>((ok, fail) => {
×
223
            this.client.set(
×
224
                key,
×
225
                value,
×
226
                "PX",
×
227
                duration,
×
228
                (err: any, result: any) => {
×
229
                    if (err) return fail(err)
×
230
                    ok()
×
231
                },
×
232
            )
×
233
        })
×
234
    }
×
235

26✔
236
    /**
26✔
237
     * Clears everything stored in the cache.
26✔
238
     * @param queryRunner
26✔
239
     */
26✔
240
    async clear(queryRunner?: QueryRunner): Promise<void> {
26✔
241
        if (this.isRedis5OrHigher()) {
×
242
            // Redis 5+ Promise-based API
×
243
            await this.client.flushDb()
×
244
            return
×
245
        }
×
246

×
247
        // Redis 3/4 callback-based API
×
248
        return new Promise<void>((ok, fail) => {
×
249
            this.client.flushdb((err: any, result: any) => {
×
250
                if (err) return fail(err)
×
251
                ok()
×
252
            })
×
253
        })
×
254
    }
×
255

26✔
256
    /**
26✔
257
     * Removes all cached results by given identifiers from cache.
26✔
258
     * @param identifiers
26✔
259
     * @param queryRunner
26✔
260
     */
26✔
261
    async remove(
26✔
262
        identifiers: string[],
×
263
        queryRunner?: QueryRunner,
×
264
    ): Promise<void> {
×
265
        await Promise.all(
×
266
            identifiers.map((identifier) => {
×
267
                return this.deleteKey(identifier)
×
268
            }),
×
269
        )
×
270
    }
×
271

26✔
272
    // -------------------------------------------------------------------------
26✔
273
    // Protected Methods
26✔
274
    // -------------------------------------------------------------------------
26✔
275

26✔
276
    /**
26✔
277
     * Removes a single key from redis database.
26✔
278
     * @param key
26✔
279
     */
26✔
280
    protected async deleteKey(key: string): Promise<void> {
26✔
281
        if (this.isRedis5OrHigher()) {
×
282
            // Redis 5+ Promise-based API
×
283
            await this.client.del(key)
×
284
            return
×
285
        }
×
286

×
287
        // Redis 3/4 callback-based API
×
288
        return new Promise<void>((ok, fail) => {
×
289
            this.client.del(key, (err: any, result: any) => {
×
290
                if (err) return fail(err)
×
291
                ok()
×
292
            })
×
293
        })
×
294
    }
×
295

26✔
296
    /**
26✔
297
     * Loads redis dependency.
26✔
298
     */
26✔
299
    protected loadRedis(): any {
26✔
300
        try {
130✔
301
            if (this.clientType === "ioredis/cluster") {
130!
302
                return PlatformTools.load("ioredis")
×
303
            } else {
130✔
304
                return PlatformTools.load(this.clientType)
130✔
305
            }
130✔
306
        } catch {
130!
307
            throw new TypeORMError(
×
308
                `Cannot use cache because ${this.clientType} is not installed. Please run "npm i ${this.clientType}".`,
×
309
            )
×
310
        }
×
311
    }
130✔
312

26✔
313
    /**
26✔
314
     * Detects the Redis package version by reading the installed package.json
26✔
315
     * and sets the appropriate API version (3 for callback-based, 5 for Promise-based).
26✔
316
     */
26✔
317
    private detectRedisVersion(): void {
26✔
318
        if (this.clientType !== "redis") return
130!
319
        const version = PlatformTools.readPackageVersion("redis")
130✔
320
        const major = parseInt(version.split(".")[0], 10)
130✔
321
        if (isNaN(major)) {
130!
322
            throw new TypeORMError(`Invalid Redis version format: ${version}`)
×
323
        }
×
324
        if (major <= 4) {
130✔
325
            // Redis 3/4 uses callback-based API
52✔
326
            this.redisMajorVersion = 3
52✔
327
        } else {
130✔
328
            // Redis 5+ uses Promise-based API
78✔
329
            this.redisMajorVersion = 5
78✔
330
        }
78✔
331
    }
130✔
332

26✔
333
    /**
26✔
334
     * Checks if Redis version is 5.x or higher
26✔
335
     */
26✔
336
    private isRedis5OrHigher(): boolean {
26✔
337
        if (this.clientType !== "redis") return false
×
338
        return (
×
339
            this.redisMajorVersion !== undefined && this.redisMajorVersion >= 5
×
340
        )
×
341
    }
×
342
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc