Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -80,6 +80,6 @@
},
"peerDependencies": {
"newrelic": "^12.0.0",
"ioredis": "^5.9.1"
"redis": "5.10.0"
}
}
20 changes: 16 additions & 4 deletions src/httpClient/redisStorage.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
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';
Copy link
Collaborator

Choose a reason for hiding this comment

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


const KEY_PREFIX = 'axios-cache-';

const MIN_TTL = 60000;

export default function createRedisStorage(redisEndpoint: string) {
// eslint-disable-next-line import/no-extraneous-dependencies
const Redis = require('ioredis');
export default function createRedisStorage(client: ReturnType<typeof createClient>) {
const connectionPromise: Promise<any> = client.connect();

const client = new Redis(redisEndpoint);
const connectIfNeeded = async () => {
if (client.isReady) {
return;
}

await connectionPromise;
};

// 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
Expand All @@ -38,6 +49,7 @@ export default function createRedisStorage(redisEndpoint: string) {
},

async remove(key) {
await connectIfNeeded();
await client.del(`${KEY_PREFIX}${key}`);
},
});
Expand Down
47 changes: 47 additions & 0 deletions tests/httpClient/redisStorage.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});