From f0a26fa38078da5e0808c84c19bf6735f98becd1 Mon Sep 17 00:00:00 2001 From: "matus.hudec" Date: Fri, 16 Jan 2026 16:49:45 +0100 Subject: [PATCH 1/5] Release 7.0.6 --- CHANGELOG.md | 8 +++++++- package.json | 2 +- src/httpClient/redisStorage.ts | 28 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a1655..1942555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -## [7.0.5] - 2025-11-14 +## [7.0.6] - 2026-01-19 + +### Changed + +- Switch back to redis, fix concurrent redis connection issues + +## [7.0.5] - 2026-01-14 ### Changed diff --git a/package.json b/package.json index 71d25b9..77e991d 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,6 @@ }, "peerDependencies": { "newrelic": "^12.0.0", - "ioredis": "^5.9.1" + "redis": "^5.10.0" } } diff --git a/src/httpClient/redisStorage.ts b/src/httpClient/redisStorage.ts index d785c56..7fe12fb 100644 --- a/src/httpClient/redisStorage.ts +++ b/src/httpClient/redisStorage.ts @@ -5,21 +5,44 @@ const KEY_PREFIX = 'axios-cache-'; const MIN_TTL = 60000; +let connectingPromise: Promise | null = null; + export default function createRedisStorage(redisEndpoint: string) { // eslint-disable-next-line import/no-extraneous-dependencies - const Redis = require('ioredis'); + const redis = require('redis'); + + const client = redis.createClient({ url: redisEndpoint }); + + const connectIfNeeded = async () => { + if (client.isReady) { + return; + } - const client = new Redis(redisEndpoint); + if (connectingPromise) { + await connectingPromise; + return; + } + + connectingPromise = client.connect(); + try { + await connectingPromise; + } finally { + connectingPromise = null; + } + }; // source https://axios-cache-interceptor.js.org/guide/storages#node-redis-storage return buildStorage({ async find(key) { + await connectIfNeeded(); const result = await client.get(`${KEY_PREFIX}${key}`); return result ? (JSON.parse(result) as StorageValue) : undefined; }, // eslint-disable-next-line complexity async set(key, value, req) { + await connectIfNeeded(); + await client.set(`${KEY_PREFIX}${key}`, JSON.stringify(value), { PXAT: // We don't want to keep indefinitely values in the storage if @@ -38,6 +61,7 @@ export default function createRedisStorage(redisEndpoint: string) { }, async remove(key) { + await connectIfNeeded(); await client.del(`${KEY_PREFIX}${key}`); }, }); From 1af6b12f524bda08900a70c3a10b54b772891df1 Mon Sep 17 00:00:00 2001 From: "matus.hudec" Date: Fri, 16 Jan 2026 17:30:11 +0100 Subject: [PATCH 2/5] Release 7.0.6 --- package.json | 2 +- src/httpClient/redisStorage.ts | 24 ++++++------------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 77e991d..f0cb4c3 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,6 @@ }, "peerDependencies": { "newrelic": "^12.0.0", - "redis": "^5.10.0" + "redis": "5.10.0" } } diff --git a/src/httpClient/redisStorage.ts b/src/httpClient/redisStorage.ts index 7fe12fb..3f55331 100644 --- a/src/httpClient/redisStorage.ts +++ b/src/httpClient/redisStorage.ts @@ -1,34 +1,22 @@ import { buildStorage, canStale } from 'axios-cache-interceptor'; import type { StorageValue } from 'axios-cache-interceptor'; +// eslint-disable-next-line import/no-extraneous-dependencies +// @ts-ignore +import { createClient } from 'redis'; const KEY_PREFIX = 'axios-cache-'; const MIN_TTL = 60000; -let connectingPromise: Promise | null = null; - -export default function createRedisStorage(redisEndpoint: string) { - // eslint-disable-next-line import/no-extraneous-dependencies - const redis = require('redis'); - - const client = redis.createClient({ url: redisEndpoint }); +export default function createRedisStorage(client: ReturnType) { + let connectionPromise: Promise = client.connect(); const connectIfNeeded = async () => { if (client.isReady) { return; } - if (connectingPromise) { - await connectingPromise; - return; - } - - connectingPromise = client.connect(); - try { - await connectingPromise; - } finally { - connectingPromise = null; - } + await connectionPromise; }; // source https://axios-cache-interceptor.js.org/guide/storages#node-redis-storage From 01377e6d8ea96412ade70d3ad95b5f842b7501ec Mon Sep 17 00:00:00 2001 From: "matus.hudec" Date: Fri, 16 Jan 2026 17:35:41 +0100 Subject: [PATCH 3/5] Release 7.0.6 --- src/httpClient/redisStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpClient/redisStorage.ts b/src/httpClient/redisStorage.ts index 3f55331..11d0315 100644 --- a/src/httpClient/redisStorage.ts +++ b/src/httpClient/redisStorage.ts @@ -9,7 +9,7 @@ const KEY_PREFIX = 'axios-cache-'; const MIN_TTL = 60000; export default function createRedisStorage(client: ReturnType) { - let connectionPromise: Promise = client.connect(); + const connectionPromise: Promise = client.connect(); const connectIfNeeded = async () => { if (client.isReady) { From 582a817623ef7fe4d4caffd20c89df2877b031c2 Mon Sep 17 00:00:00 2001 From: "matus.hudec" Date: Fri, 23 Jan 2026 17:21:53 +0100 Subject: [PATCH 4/5] Release 7.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0cb4c3..ef5fea3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lambda-essentials-ts", - "version": "7.0.5", + "version": "7.0.6", "description": "A selection of the finest modules supporting authorization, API routing, error handling, logging and sending HTTP requests.", "main": "lib/index.js", "private": false, From c6eb1b38254418a2acdaa1e8f9451eef2e14521b Mon Sep 17 00:00:00 2001 From: "matus.hudec" Date: Fri, 23 Jan 2026 17:23:48 +0100 Subject: [PATCH 5/5] Release 7.0.6 --- tests/httpClient/redisStorage.test.ts | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/httpClient/redisStorage.test.ts diff --git a/tests/httpClient/redisStorage.test.ts b/tests/httpClient/redisStorage.test.ts new file mode 100644 index 0000000..8699ef8 --- /dev/null +++ b/tests/httpClient/redisStorage.test.ts @@ -0,0 +1,47 @@ +jest.mock( + 'redis', + () => ({ + createClient: jest.fn(), + }), + { virtual: true }, +); + +import createRedisStorage from '../../src/httpClient/redisStorage'; + +const mClient = { + connect: jest.fn().mockImplementation(async () => { + await new Promise((resolve) => setTimeout(resolve, 50)); + mClient.isReady = true; + }), + get: jest.fn().mockResolvedValue(null), + set: jest.fn().mockResolvedValue('OK'), + del: jest.fn().mockResolvedValue(1), + isReady: false, + on: jest.fn(), +}; + +describe('redisStorage', () => { + beforeEach(() => { + jest.clearAllMocks(); + mClient.isReady = false; + }); + + it('should connect when not ready', async () => { + const storage = createRedisStorage(mClient as any); + mClient.isReady = false; + + await storage.get('key'); + + expect(mClient.connect).toHaveBeenCalledTimes(1); + expect(mClient.isReady).toBe(true); + }); + + it('should connect only once when multiple requests are made', async () => { + mClient.isReady = false; + const storage = createRedisStorage(mClient as any); + + await Promise.all([storage.get('key1'), storage.get('key2')]); + + expect(mClient.connect).toHaveBeenCalledTimes(1); + }); +});