Skip to content

Commit 76abafa

Browse files
committed
Upgrade Redis to version 5
1 parent 69490e2 commit 76abafa

File tree

11 files changed

+178
-252
lines changed

11 files changed

+178
-252
lines changed

apps/cache-testing/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"next": "15.3.2",
2222
"react": "19.1.0",
2323
"react-dom": "19.1.0",
24-
"redis": "4.7.0"
24+
"redis": "5.0.1"
2525
},
2626
"devDependencies": {
2727
"@playwright/test": "1.52.0",

internal/eslint-config/vitest.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import tseslint from 'typescript-eslint';
44
export default tseslint.config({
55
files: ['**/*.test.ts', '**/*.spec.ts'],
66
extends: [vitest.configs.all],
7+
settings: {
8+
vitest: {
9+
typecheck: true,
10+
},
11+
},
712
rules: {
813
'vitest/prefer-expect-assertions': [
914
'error',

packages/cache-handler/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@
5555
},
5656
"peerDependencies": {
5757
"next": ">= 15 < 16",
58-
"redis": ">= 4.6"
58+
"redis": ">= 5 < 6"
5959
}
6060
}

packages/cache-handler/src/handlers/experimental-redis-cluster.ts

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import calculate from 'cluster-key-slot';
22
import { REVALIDATED_TAGS_KEY } from '../constants.js';
3-
import { createRedisTimeoutConfig } from '../helpers/create-redis-timeout-config.js';
43
import { isTagImplicit } from '../helpers/is-tag-implicit.js';
54
import type { CacheHandlerValue, Handler } from '../cache-handler.js';
65
import type { CreateRedisStringsHandlerOptions } from '../common-types.js';
@@ -103,10 +102,9 @@ export default function createHandler({
103102
key,
104103
{ implicitTags },
105104
): Promise<CacheHandlerValue | null | undefined> {
106-
const result = await cluster.get(
107-
createRedisTimeoutConfig(timeoutMs),
108-
keyPrefix + key,
109-
);
105+
const result = await cluster
106+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
107+
.get(keyPrefix + key);
110108

111109
if (!result) {
112110
return null;
@@ -124,21 +122,18 @@ export default function createHandler({
124122
return cacheValue;
125123
}
126124

127-
const revalidationTimes = await cluster.hmGet(
128-
createRedisTimeoutConfig(timeoutMs),
129-
revalidatedTagsKey,
130-
Array.from(combinedTags),
131-
);
125+
const revalidationTimes = await cluster
126+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
127+
.hmGet(revalidatedTagsKey, Array.from(combinedTags));
132128

133129
for (const timeString of revalidationTimes) {
134130
if (
135131
timeString &&
136132
Number.parseInt(timeString, 10) > cacheValue.lastModified
137133
) {
138-
await cluster.unlink(
139-
createRedisTimeoutConfig(timeoutMs),
140-
keyPrefix + key,
141-
);
134+
await cluster
135+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
136+
.unlink(keyPrefix + key);
142137

143138
return null;
144139
}
@@ -147,16 +142,17 @@ export default function createHandler({
147142
return cacheValue;
148143
},
149144
async set(key, cacheHandlerValue): Promise<void> {
150-
const options = createRedisTimeoutConfig(timeoutMs);
145+
const options = {
146+
abortSignal: AbortSignal.timeout(timeoutMs),
147+
};
151148

152149
let setOperation: Promise<string | null>;
153150

154-
let expireOperation: Promise<boolean> | undefined;
151+
let expireOperation: Promise<number> | undefined;
155152

156153
switch (keyExpirationStrategy) {
157154
case 'EXAT': {
158-
setOperation = cluster.set(
159-
options,
155+
setOperation = cluster.withCommandOptions(options).set(
160156
keyPrefix + key,
161157
JSON.stringify(cacheHandlerValue),
162158
typeof cacheHandlerValue.lifespan?.expireAt === 'number'
@@ -168,18 +164,20 @@ export default function createHandler({
168164
break;
169165
}
170166
case 'EXPIREAT': {
171-
setOperation = cluster.set(
172-
options,
167+
setOperation = cluster.withCommandOptions(options).set(
173168
keyPrefix + key,
174169
JSON.stringify(cacheHandlerValue),
170+
typeof cacheHandlerValue.lifespan?.expireAt === 'number'
171+
? {
172+
EXAT: cacheHandlerValue.lifespan.expireAt,
173+
}
174+
: undefined,
175175
);
176176

177177
expireOperation = cacheHandlerValue.lifespan
178-
? cluster.expireAt(
179-
options,
180-
keyPrefix + key,
181-
cacheHandlerValue.lifespan.expireAt,
182-
)
178+
? cluster
179+
.withCommandOptions(options)
180+
.expireAt(keyPrefix + key, cacheHandlerValue.lifespan.expireAt)
183181
: undefined;
184182
break;
185183
}
@@ -192,12 +190,13 @@ export default function createHandler({
192190

193191
const setTagsOperation =
194192
cacheHandlerValue.tags.length > 0
195-
? cluster.hSet(
196-
options,
197-
keyPrefix + sharedTagsKey,
198-
key,
199-
JSON.stringify(cacheHandlerValue.tags),
200-
)
193+
? cluster
194+
.withCommandOptions(options)
195+
.hSet(
196+
keyPrefix + sharedTagsKey,
197+
key,
198+
JSON.stringify(cacheHandlerValue.tags),
199+
)
201200
: undefined;
202201

203202
await Promise.all([setOperation, expireOperation, setTagsOperation]);
@@ -206,34 +205,28 @@ export default function createHandler({
206205
// If the tag is an implicit tag, we need to mark it as revalidated.
207206
// The revalidation process is done by the CacheHandler class on the next get operation.
208207
if (isTagImplicit(tag)) {
209-
await cluster.hSet(
210-
createRedisTimeoutConfig(timeoutMs),
211-
revalidatedTagsKey,
212-
tag,
213-
Date.now(),
214-
);
208+
await cluster
209+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
210+
.hSet(revalidatedTagsKey, tag, Date.now());
215211
}
216212

217213
const tagsMap: Map<string, string[]> = new Map();
218214

219-
let cursor = 0;
215+
let cursor = '0';
220216

221217
const hScanOptions = { COUNT: revalidateTagQuerySize };
222218

223219
do {
224-
const remoteTagsPortion = await cluster.hScan(
225-
createRedisTimeoutConfig(timeoutMs),
226-
keyPrefix + sharedTagsKey,
227-
cursor,
228-
hScanOptions,
229-
);
220+
const remoteTagsPortion = await cluster
221+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
222+
.hScan(keyPrefix + sharedTagsKey, cursor, hScanOptions);
230223

231-
for (const { field, value } of remoteTagsPortion.tuples) {
224+
for (const { field, value } of remoteTagsPortion.entries) {
232225
tagsMap.set(field, JSON.parse(value) as string[]);
233226
}
234227

235228
cursor = remoteTagsPortion.cursor;
236-
} while (cursor !== 0);
229+
} while (cursor !== '0');
237230

238231
const keysToDelete: string[] = [];
239232

@@ -256,27 +249,28 @@ export default function createHandler({
256249

257250
for (const [slot, keys] of slotKeysMap) {
258251
const targetMasterNode = cluster.slots[slot]?.master;
259-
const client = await targetMasterNode?.client;
252+
253+
const client = targetMasterNode?.client;
260254

261255
if (keys.length === 0 || !client) {
262256
continue;
263257
}
264258

265259
unlinkPromises.push(
266-
client.unlink(createRedisTimeoutConfig(timeoutMs), keys),
260+
client.withAbortSignal(AbortSignal.timeout(timeoutMs)).unlink(keys),
267261
);
268262
}
269263

270-
const updateTagsOperation = cluster.hDel(
271-
{ isolated: true, ...createRedisTimeoutConfig(timeoutMs) },
272-
keyPrefix + sharedTagsKey,
273-
tagsToDelete,
274-
);
264+
const updateTagsOperation = cluster
265+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
266+
.hDel(keyPrefix + sharedTagsKey, tagsToDelete);
275267

276268
await Promise.allSettled([...unlinkPromises, updateTagsOperation]);
277269
},
278270
async delete(key): Promise<void> {
279-
await cluster.unlink(createRedisTimeoutConfig(timeoutMs), key);
271+
await cluster
272+
.withCommandOptions({ abortSignal: AbortSignal.timeout(timeoutMs) })
273+
.unlink(key);
280274
},
281275
};
282276
}

packages/cache-handler/src/handlers/redis-stack.ts

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { randomBytes } from 'node:crypto';
2-
import { ErrorReply, SchemaFieldTypes } from 'redis';
2+
import { ErrorReply, SCHEMA_FIELD_TYPE } from 'redis';
33
import { REVALIDATED_TAGS_KEY, TIME_ONE_YEAR } from '../constants.js';
4-
import { createRedisTimeoutConfig } from '../helpers/create-redis-timeout-config.js';
54
import { isTagImplicit } from '../helpers/is-tag-implicit.js';
65
import type { CacheHandlerValue, Handler } from '../cache-handler.js';
76
import type {
@@ -11,6 +10,11 @@ import type {
1110

1211
export type { CreateRedisStackHandlerOptions };
1312

13+
type SearchNoContentReply = {
14+
total: number;
15+
documents: string[];
16+
};
17+
1418
/**
1519
* Creates a Handler for handling cache operations using Redis JSON.
1620
*
@@ -85,7 +89,7 @@ export default function createHandler({
8589
await client.ft.create(
8690
indexName,
8791
{
88-
'$.tags': { type: SchemaFieldTypes.TEXT, AS: 'tag' },
92+
'$.tags': { type: SCHEMA_FIELD_TYPE.TEXT, AS: 'tag' },
8993
},
9094
{
9195
ON: 'JSON',
@@ -114,10 +118,9 @@ export default function createHandler({
114118
): Promise<CacheHandlerValue | null | undefined> {
115119
assertClientIsReady();
116120

117-
const cacheValue = (await client.json.get(
118-
createRedisTimeoutConfig(timeoutMs),
119-
keyPrefix + key,
120-
)) as CacheHandlerValue | null;
121+
const cacheValue = (await client
122+
.withAbortSignal(AbortSignal.timeout(timeoutMs))
123+
.json.get(keyPrefix + key)) as CacheHandlerValue | null;
121124

122125
if (!cacheValue) {
123126
return null;
@@ -134,21 +137,18 @@ export default function createHandler({
134137
return cacheValue;
135138
}
136139

137-
const revalidationTimes = await client.hmGet(
138-
createRedisTimeoutConfig(timeoutMs),
139-
revalidatedTagsKey,
140-
Array.from(combinedTags),
141-
);
140+
const revalidationTimes = await client
141+
.withAbortSignal(AbortSignal.timeout(timeoutMs))
142+
.hmGet(revalidatedTagsKey, Array.from(combinedTags));
142143

143144
for (const timeString of revalidationTimes) {
144145
if (
145146
timeString &&
146147
Number.parseInt(timeString, 10) > cacheValue.lastModified
147148
) {
148-
await client.unlink(
149-
createRedisTimeoutConfig(timeoutMs),
150-
keyPrefix + key,
151-
);
149+
await client
150+
.withAbortSignal(AbortSignal.timeout(timeoutMs))
151+
.unlink(keyPrefix + key);
152152

153153
return null;
154154
}
@@ -161,21 +161,20 @@ export default function createHandler({
161161

162162
cacheHandlerValue.tags = cacheHandlerValue.tags.map(sanitizeTag);
163163

164-
const options = createRedisTimeoutConfig(timeoutMs);
164+
const signal = AbortSignal.timeout(timeoutMs);
165165

166-
const setCacheValue = client.json.set(
167-
options,
168-
keyPrefix + key,
169-
'.',
170-
cacheHandlerValue as unknown as RedisJSON,
171-
);
166+
const setCacheValue = client
167+
.withAbortSignal(signal)
168+
.json.set(
169+
keyPrefix + key,
170+
'.',
171+
cacheHandlerValue as unknown as RedisJSON,
172+
);
172173

173174
const expireCacheValue = cacheHandlerValue.lifespan
174-
? client.expireAt(
175-
options,
176-
keyPrefix + key,
177-
cacheHandlerValue.lifespan.expireAt,
178-
)
175+
? client
176+
.withAbortSignal(signal)
177+
.expireAt(keyPrefix + key, cacheHandlerValue.lifespan.expireAt)
179178
: undefined;
180179

181180
await Promise.all([setCacheValue, expireCacheValue]);
@@ -190,28 +189,24 @@ export default function createHandler({
190189
// If the tag is an implicit tag, we need to mark it as revalidated.
191190
// The revalidation process is done by the CacheHandler class on the next get operation.
192191
if (isTagImplicit(tag)) {
193-
await client.hSet(
194-
createRedisTimeoutConfig(timeoutMs),
195-
revalidatedTagsKey,
196-
sanitizedTag,
197-
Date.now(),
198-
);
192+
await client
193+
.withAbortSignal(AbortSignal.timeout(timeoutMs))
194+
.hSet(revalidatedTagsKey, sanitizedTag, Date.now());
199195
}
200196

201197
let from = 0;
202198

203199
const keysToDelete: string[] = [];
204200

205201
while (true) {
206-
const { documents: documentIds } = await client.ft.searchNoContent(
207-
createRedisTimeoutConfig(timeoutMs),
202+
const { documents: documentIds } = (await client.ft.searchNoContent(
208203
indexName,
209204
`@tag:(${sanitizedTag})`,
210205
{
211206
LIMIT: { from, size: revalidateTagQuerySize },
212207
TIMEOUT: timeoutMs,
213208
},
214-
);
209+
)) as SearchNoContentReply;
215210

216211
for (const id of documentIds) {
217212
keysToDelete.push(id);
@@ -228,12 +223,12 @@ export default function createHandler({
228223
return;
229224
}
230225

231-
const options = createRedisTimeoutConfig(timeoutMs);
226+
const signal = AbortSignal.timeout(timeoutMs);
232227

233-
await client.unlink(options, keysToDelete);
228+
await client.withAbortSignal(signal).unlink(keysToDelete);
234229
},
235230
async delete(key): Promise<void> {
236-
await client.unlink(createRedisTimeoutConfig(timeoutMs), key);
231+
await client.withAbortSignal(AbortSignal.timeout(timeoutMs)).unlink(key);
237232
},
238233
};
239234
}

0 commit comments

Comments
 (0)