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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

33.43
/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 connection: DataSource,
×
42
        clientType: "redis" | "ioredis" | "ioredis/cluster",
×
43
    ) {
×
44
        this.clientType = clientType
×
45
        this.redis = this.loadRedis()
×
46
    }
×
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✔
56
        const cacheOptions: any = this.connection.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 (
×
77
                typeof this.connection.options.cache === "object" &&
×
78
                this.connection.options.cache.ignoreErrors
×
79
            ) {
×
80
                this.client.on("error", (err: any) => {
×
81
                    this.connection.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
     */
26✔
155
    async synchronize(queryRunner: QueryRunner): Promise<void> {}
26✔
156

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

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

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

26✔
185
    /**
26✔
186
     * Checks if cache is expired or not.
26✔
187
     */
26✔
188
    isExpired(savedCache: QueryResultCacheOptions): boolean {
26✔
189
        return savedCache.time! + savedCache.duration < Date.now()
×
190
    }
×
191

26✔
192
    /**
26✔
193
     * Stores given query result in the cache.
26✔
194
     */
26✔
195
    async storeInCache(
26✔
196
        options: QueryResultCacheOptions,
×
197
        savedCache: QueryResultCacheOptions,
×
198
        queryRunner?: QueryRunner,
×
199
    ): Promise<void> {
×
200
        const key = options.identifier || options.query
×
201
        if (!key) return
×
202

×
203
        const value = JSON.stringify(options)
×
204
        const duration = options.duration
×
205

×
206
        if (this.isRedis5OrHigher()) {
×
207
            // Redis 5+ Promise-based API with PX option
×
208
            await this.client.set(key, value, {
×
209
                PX: duration,
×
210
            })
×
211
            return
×
212
        }
×
213

×
214
        // Redis 3/4 callback-based API
×
215
        return new Promise<void>((ok, fail) => {
×
216
            this.client.set(
×
217
                key,
×
218
                value,
×
219
                "PX",
×
220
                duration,
×
221
                (err: any, result: any) => {
×
222
                    if (err) return fail(err)
×
223
                    ok()
×
224
                },
×
225
            )
×
226
        })
×
227
    }
×
228

26✔
229
    /**
26✔
230
     * Clears everything stored in the cache.
26✔
231
     */
26✔
232
    async clear(queryRunner?: QueryRunner): Promise<void> {
26✔
233
        if (this.isRedis5OrHigher()) {
×
234
            // Redis 5+ Promise-based API
×
235
            await this.client.flushDb()
×
236
            return
×
237
        }
×
238

×
239
        // Redis 3/4 callback-based API
×
240
        return new Promise<void>((ok, fail) => {
×
241
            this.client.flushdb((err: any, result: any) => {
×
242
                if (err) return fail(err)
×
243
                ok()
×
244
            })
×
245
        })
×
246
    }
×
247

26✔
248
    /**
26✔
249
     * Removes all cached results by given identifiers from cache.
26✔
250
     */
26✔
251
    async remove(
26✔
252
        identifiers: string[],
×
253
        queryRunner?: QueryRunner,
×
254
    ): Promise<void> {
×
255
        await Promise.all(
×
256
            identifiers.map((identifier) => {
×
257
                return this.deleteKey(identifier)
×
258
            }),
×
259
        )
×
260
    }
×
261

26✔
262
    // -------------------------------------------------------------------------
26✔
263
    // Protected Methods
26✔
264
    // -------------------------------------------------------------------------
26✔
265

26✔
266
    /**
26✔
267
     * Removes a single key from redis database.
26✔
268
     */
26✔
269
    protected async deleteKey(key: string): Promise<void> {
26✔
270
        if (this.isRedis5OrHigher()) {
×
271
            // Redis 5+ Promise-based API
×
272
            await this.client.del(key)
×
273
            return
×
274
        }
×
275

×
276
        // Redis 3/4 callback-based API
×
277
        return new Promise<void>((ok, fail) => {
×
278
            this.client.del(key, (err: any, result: any) => {
×
279
                if (err) return fail(err)
×
280
                ok()
×
281
            })
×
282
        })
×
283
    }
×
284

26✔
285
    /**
26✔
286
     * Loads redis dependency.
26✔
287
     */
26✔
288
    protected loadRedis(): any {
26✔
289
        try {
×
290
            if (this.clientType === "ioredis/cluster") {
×
291
                return PlatformTools.load("ioredis")
×
292
            } else {
×
293
                return PlatformTools.load(this.clientType)
×
294
            }
×
295
        } catch {
×
296
            throw new TypeORMError(
×
297
                `Cannot use cache because ${this.clientType} is not installed. Please run "npm i ${this.clientType}".`,
×
298
            )
×
299
        }
×
300
    }
×
301

26✔
302
    /**
26✔
303
     * Detects the Redis version based on the connected client's API characteristics
26✔
304
     * without creating test keys in the database
26✔
305
     */
26✔
306
    private detectRedisVersion(): void {
26✔
307
        if (this.clientType !== "redis") return
×
308

×
309
        try {
×
310
            // Detect version by examining the client's method signatures
×
311
            // This avoids creating test keys in the database
×
312
            const setMethod = this.client.set
×
313
            if (setMethod && setMethod.length <= 3) {
×
314
                // Redis 5+ set method accepts fewer parameters (key, value, options)
×
315
                this.redisMajorVersion = 5
×
316
            } else {
×
317
                // Redis 3/4 set method requires more parameters (key, value, flag, duration, callback)
×
318
                this.redisMajorVersion = 3
×
319
            }
×
320
        } catch {
×
321
            // Default to Redis 3/4 for maximum compatibility
×
322
            this.redisMajorVersion = 3
×
323
        }
×
324
    }
×
325

26✔
326
    /**
26✔
327
     * Checks if Redis version is 5.x or higher
26✔
328
     */
26✔
329
    private isRedis5OrHigher(): boolean {
26✔
330
        if (this.clientType !== "redis") return false
×
331
        return (
×
332
            this.redisMajorVersion !== undefined && this.redisMajorVersion >= 5
×
333
        )
×
334
    }
×
335
}
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