@mrjasonroy/cache-components-cache-handler 16.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +437 -0
- package/dist/data-cache/memory.d.ts +47 -0
- package/dist/data-cache/memory.d.ts.map +1 -0
- package/dist/data-cache/memory.js +310 -0
- package/dist/data-cache/memory.js.map +1 -0
- package/dist/data-cache/redis.d.ts +83 -0
- package/dist/data-cache/redis.d.ts.map +1 -0
- package/dist/data-cache/redis.js +240 -0
- package/dist/data-cache/redis.js.map +1 -0
- package/dist/data-cache/types.d.ts +85 -0
- package/dist/data-cache/types.d.ts.map +1 -0
- package/dist/data-cache/types.js +7 -0
- package/dist/data-cache/types.js.map +1 -0
- package/dist/handlers/composite.d.ts +70 -0
- package/dist/handlers/composite.d.ts.map +1 -0
- package/dist/handlers/composite.js +123 -0
- package/dist/handlers/composite.js.map +1 -0
- package/dist/handlers/memory.d.ts +77 -0
- package/dist/handlers/memory.d.ts.map +1 -0
- package/dist/handlers/memory.js +145 -0
- package/dist/handlers/memory.js.map +1 -0
- package/dist/handlers/redis.d.ts +80 -0
- package/dist/handlers/redis.d.ts.map +1 -0
- package/dist/handlers/redis.js +210 -0
- package/dist/handlers/redis.js.map +1 -0
- package/dist/helpers/buffer.d.ts +25 -0
- package/dist/helpers/buffer.d.ts.map +1 -0
- package/dist/helpers/buffer.js +45 -0
- package/dist/helpers/buffer.js.map +1 -0
- package/dist/helpers/is-implicit-tag.d.ts +9 -0
- package/dist/helpers/is-implicit-tag.d.ts.map +1 -0
- package/dist/helpers/is-implicit-tag.js +16 -0
- package/dist/helpers/is-implicit-tag.js.map +1 -0
- package/dist/helpers/lifespan.d.ts +18 -0
- package/dist/helpers/lifespan.d.ts.map +1 -0
- package/dist/helpers/lifespan.js +43 -0
- package/dist/helpers/lifespan.js.map +1 -0
- package/dist/helpers/next-config.d.ts +97 -0
- package/dist/helpers/next-config.d.ts.map +1 -0
- package/dist/helpers/next-config.js +96 -0
- package/dist/helpers/next-config.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +163 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory-based Data Cache Handler for "use cache" directive
|
|
3
|
+
* Based on Next.js default handler but with customizable options
|
|
4
|
+
*
|
|
5
|
+
* In-memory caches are fragile and should not use stale-while-revalidate
|
|
6
|
+
* semantics because entries may be evicted before reuse. Stale entries
|
|
7
|
+
* are considered expired/missing.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Simple LRU cache implementation
|
|
11
|
+
*/
|
|
12
|
+
class LRUCache {
|
|
13
|
+
cache = new Map();
|
|
14
|
+
maxSize;
|
|
15
|
+
sizeOf;
|
|
16
|
+
currentSize = 0;
|
|
17
|
+
constructor(maxSize, sizeOf) {
|
|
18
|
+
this.maxSize = maxSize;
|
|
19
|
+
this.sizeOf = sizeOf;
|
|
20
|
+
}
|
|
21
|
+
get(key) {
|
|
22
|
+
const value = this.cache.get(key);
|
|
23
|
+
if (value !== undefined) {
|
|
24
|
+
// Move to end (most recently used)
|
|
25
|
+
this.cache.delete(key);
|
|
26
|
+
this.cache.set(key, value);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
set(key, value) {
|
|
31
|
+
// Remove existing if present
|
|
32
|
+
const existing = this.cache.get(key);
|
|
33
|
+
if (existing !== undefined) {
|
|
34
|
+
this.currentSize -= this.sizeOf(existing);
|
|
35
|
+
this.cache.delete(key);
|
|
36
|
+
}
|
|
37
|
+
const size = this.sizeOf(value);
|
|
38
|
+
// Evict oldest entries if needed
|
|
39
|
+
while (this.currentSize + size > this.maxSize && this.cache.size > 0) {
|
|
40
|
+
const firstKey = this.cache.keys().next().value;
|
|
41
|
+
if (firstKey !== undefined) {
|
|
42
|
+
const firstValue = this.cache.get(firstKey);
|
|
43
|
+
if (firstValue !== undefined) {
|
|
44
|
+
this.currentSize -= this.sizeOf(firstValue);
|
|
45
|
+
}
|
|
46
|
+
this.cache.delete(firstKey);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Add new entry
|
|
50
|
+
this.cache.set(key, value);
|
|
51
|
+
this.currentSize += size;
|
|
52
|
+
}
|
|
53
|
+
delete(key) {
|
|
54
|
+
const value = this.cache.get(key);
|
|
55
|
+
if (value !== undefined) {
|
|
56
|
+
this.currentSize -= this.sizeOf(value);
|
|
57
|
+
this.cache.delete(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
clear() {
|
|
61
|
+
this.cache.clear();
|
|
62
|
+
this.currentSize = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Global tags manifest shared across all handler instances
|
|
67
|
+
*
|
|
68
|
+
* This is necessary because Next.js may create multiple handler instances
|
|
69
|
+
* (e.g., one for "default" profile and one for "remote" profile), but
|
|
70
|
+
* revalidateTag() needs to invalidate entries across ALL instances.
|
|
71
|
+
*
|
|
72
|
+
* ## Tag Revalidation Semantics
|
|
73
|
+
*
|
|
74
|
+
* When `updateTags()` is called, it can operate in two modes:
|
|
75
|
+
*
|
|
76
|
+
* ### 1. Immediate Expiration (deprecated, for testing)
|
|
77
|
+
* ```typescript
|
|
78
|
+
* updateTags(['my-tag'], undefined)
|
|
79
|
+
* // Sets: expired = now (entries expire immediately)
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* ### 2. Stale-While-Revalidate (recommended for production)
|
|
83
|
+
* ```typescript
|
|
84
|
+
* updateTags(['my-tag'], { expire: 3600 })
|
|
85
|
+
* // Sets: stale = now, expired = now + 3600 * 1000
|
|
86
|
+
* // Entries are marked stale immediately but won't expire for 1 hour
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* ## Entry Lifecycle
|
|
90
|
+
*
|
|
91
|
+
* When `get()` is called, entries go through these checks:
|
|
92
|
+
*
|
|
93
|
+
* 1. **TTL Check**: Entry expires if `now > timestamp + revalidate * 1000`
|
|
94
|
+
* 2. **Tag Expiration Check**: Entry expires if ANY tag was revalidated after entry creation
|
|
95
|
+
* - Skips tags with future expiration times (stale-while-revalidate)
|
|
96
|
+
* 3. **Tag Staleness Check**: Entry is marked stale (revalidate = -1) if ANY tag's stale
|
|
97
|
+
* timestamp is after entry creation
|
|
98
|
+
*
|
|
99
|
+
* This allows entries to be served with stale data while revalidating in the background.
|
|
100
|
+
*/
|
|
101
|
+
const globalTagsManifest = new Map();
|
|
102
|
+
/**
|
|
103
|
+
* Check if tags are expired based on manifest
|
|
104
|
+
* Returns true if any tag was revalidated (invalidated) after the entry was created
|
|
105
|
+
*/
|
|
106
|
+
function areTagsExpired(tags, timestamp, debug) {
|
|
107
|
+
const now = Math.round(performance.timeOrigin + performance.now());
|
|
108
|
+
for (const tag of tags) {
|
|
109
|
+
const entry = globalTagsManifest.get(tag);
|
|
110
|
+
const expiredAt = entry?.expired;
|
|
111
|
+
debug?.("areTagsExpired checking", {
|
|
112
|
+
tag,
|
|
113
|
+
entryTimestamp: timestamp,
|
|
114
|
+
expiredAt,
|
|
115
|
+
now,
|
|
116
|
+
});
|
|
117
|
+
if (typeof expiredAt === "number") {
|
|
118
|
+
// Match Next.js logic exactly:
|
|
119
|
+
// Entry is expired if:
|
|
120
|
+
// 1. expiredAt <= now (tag revalidation time has passed)
|
|
121
|
+
// 2. expiredAt > timestamp (tag was revalidated AFTER entry was created)
|
|
122
|
+
const isImmediatelyExpired = expiredAt <= now && expiredAt > timestamp;
|
|
123
|
+
debug?.("areTagsExpired result", {
|
|
124
|
+
tag,
|
|
125
|
+
isImmediatelyExpired,
|
|
126
|
+
check1: `expiredAt (${expiredAt}) <= now (${now})`,
|
|
127
|
+
check1Result: expiredAt <= now,
|
|
128
|
+
check2: `expiredAt (${expiredAt}) > timestamp (${timestamp})`,
|
|
129
|
+
check2Result: expiredAt > timestamp,
|
|
130
|
+
});
|
|
131
|
+
if (isImmediatelyExpired) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if tags are stale based on manifest
|
|
140
|
+
* Returns true if any tag was marked stale AFTER the entry was created
|
|
141
|
+
*/
|
|
142
|
+
function areTagsStale(tags, timestamp) {
|
|
143
|
+
for (const tag of tags) {
|
|
144
|
+
const entry = globalTagsManifest.get(tag);
|
|
145
|
+
const staleAt = entry?.stale ?? 0;
|
|
146
|
+
// Match Next.js logic: entry is stale if tag's stale timestamp > entry's creation timestamp
|
|
147
|
+
if (typeof staleAt === "number" && staleAt > timestamp) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create a memory-based data cache handler for "use cache" directive
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* // cache-handler.mjs
|
|
159
|
+
* import { createMemoryDataCacheHandler } from '@mrjasonroy/better-nextjs-cache-handler/data-cache';
|
|
160
|
+
*
|
|
161
|
+
* export default createMemoryDataCacheHandler({
|
|
162
|
+
* maxSize: 100 * 1024 * 1024, // 100MB
|
|
163
|
+
* debug: process.env.NODE_ENV === 'development'
|
|
164
|
+
* });
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* Then in next.config.js:
|
|
168
|
+
* ```javascript
|
|
169
|
+
* module.exports = {
|
|
170
|
+
* cacheComponents: true,
|
|
171
|
+
* cacheHandlers: {
|
|
172
|
+
* default: require.resolve('./cache-handler.mjs')
|
|
173
|
+
* }
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function createMemoryDataCacheHandler(options = {}) {
|
|
178
|
+
const maxSize = options.maxSize ?? 50 * 1024 * 1024; // 50MB default
|
|
179
|
+
// If max size is 0, return a no-op handler
|
|
180
|
+
if (maxSize === 0) {
|
|
181
|
+
return {
|
|
182
|
+
get: () => Promise.resolve(undefined),
|
|
183
|
+
set: () => Promise.resolve(),
|
|
184
|
+
refreshTags: () => Promise.resolve(),
|
|
185
|
+
getExpiration: () => Promise.resolve(0),
|
|
186
|
+
updateTags: () => Promise.resolve(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const memoryCache = new LRUCache(maxSize, (entry) => entry.size);
|
|
190
|
+
const pendingSets = new Map();
|
|
191
|
+
const debug = options.debug ? console.debug.bind(console, "[MemoryDataCache]:") : undefined;
|
|
192
|
+
return {
|
|
193
|
+
async get(cacheKey, _softTags) {
|
|
194
|
+
const pendingPromise = pendingSets.get(cacheKey);
|
|
195
|
+
if (pendingPromise) {
|
|
196
|
+
debug?.("get", cacheKey, "pending");
|
|
197
|
+
await pendingPromise;
|
|
198
|
+
}
|
|
199
|
+
const privateEntry = memoryCache.get(cacheKey);
|
|
200
|
+
if (!privateEntry) {
|
|
201
|
+
debug?.("get", cacheKey, "not found");
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
const entry = privateEntry.entry;
|
|
205
|
+
const now = performance.timeOrigin + performance.now();
|
|
206
|
+
// In-memory caches should expire after revalidate time
|
|
207
|
+
if (now > entry.timestamp + entry.revalidate * 1000) {
|
|
208
|
+
debug?.("get", cacheKey, "expired");
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
let revalidate = entry.revalidate;
|
|
212
|
+
if (areTagsExpired(entry.tags, entry.timestamp, debug)) {
|
|
213
|
+
debug?.("get", cacheKey, "had expired tag");
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
if (areTagsStale(entry.tags, entry.timestamp)) {
|
|
217
|
+
debug?.("get", cacheKey, "had stale tag");
|
|
218
|
+
revalidate = -1;
|
|
219
|
+
}
|
|
220
|
+
// Tee the stream so we can return it and keep a copy
|
|
221
|
+
const [returnStream, newSaved] = entry.value.tee();
|
|
222
|
+
entry.value = newSaved;
|
|
223
|
+
debug?.("get", cacheKey, "found", {
|
|
224
|
+
tags: entry.tags,
|
|
225
|
+
timestamp: entry.timestamp,
|
|
226
|
+
expire: entry.expire,
|
|
227
|
+
revalidate,
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
...entry,
|
|
231
|
+
revalidate,
|
|
232
|
+
value: returnStream,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
async set(cacheKey, pendingEntry) {
|
|
236
|
+
debug?.("set", cacheKey, "start");
|
|
237
|
+
let resolvePending = () => { };
|
|
238
|
+
const pendingPromise = new Promise((resolve) => {
|
|
239
|
+
resolvePending = resolve;
|
|
240
|
+
});
|
|
241
|
+
pendingSets.set(cacheKey, pendingPromise);
|
|
242
|
+
try {
|
|
243
|
+
const entry = await pendingEntry;
|
|
244
|
+
let size = 0;
|
|
245
|
+
// Tee the stream to read size and store value
|
|
246
|
+
const [value, clonedValue] = entry.value.tee();
|
|
247
|
+
entry.value = value;
|
|
248
|
+
const reader = clonedValue.getReader();
|
|
249
|
+
// Calculate total size by reading the stream
|
|
250
|
+
// biome-ignore lint/suspicious/noImplicitAnyLet: Chunk type inferred from reader
|
|
251
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: Idiomatic pattern for stream reading
|
|
252
|
+
for (let chunk; !(chunk = await reader.read()).done;) {
|
|
253
|
+
size += Buffer.from(chunk.value).byteLength;
|
|
254
|
+
}
|
|
255
|
+
memoryCache.set(cacheKey, {
|
|
256
|
+
entry,
|
|
257
|
+
isErrored: false,
|
|
258
|
+
errorRetryCount: 0,
|
|
259
|
+
size,
|
|
260
|
+
});
|
|
261
|
+
debug?.("set", cacheKey, "done", { size });
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
debug?.("set", cacheKey, "failed", err);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
resolvePending();
|
|
268
|
+
pendingSets.delete(cacheKey);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
async refreshTags() {
|
|
272
|
+
// Nothing to do for in-memory cache
|
|
273
|
+
debug?.("refreshTags", "no-op for memory cache");
|
|
274
|
+
},
|
|
275
|
+
async getExpiration(tags) {
|
|
276
|
+
const expirations = tags.map((tag) => {
|
|
277
|
+
const entry = globalTagsManifest.get(tag);
|
|
278
|
+
if (!entry)
|
|
279
|
+
return 0;
|
|
280
|
+
// Return the most recent timestamp (either expired or stale)
|
|
281
|
+
return entry.expired || 0;
|
|
282
|
+
});
|
|
283
|
+
const expiration = Math.max(...expirations, 0);
|
|
284
|
+
debug?.("getExpiration", { tags, expiration });
|
|
285
|
+
return expiration;
|
|
286
|
+
},
|
|
287
|
+
async updateTags(tags, durations) {
|
|
288
|
+
const now = Math.round(performance.timeOrigin + performance.now());
|
|
289
|
+
debug?.("updateTags", { tags, timestamp: now, durations });
|
|
290
|
+
for (const tag of tags) {
|
|
291
|
+
const existingEntry = globalTagsManifest.get(tag) || {};
|
|
292
|
+
if (durations) {
|
|
293
|
+
// Use provided durations directly
|
|
294
|
+
const updates = { ...existingEntry };
|
|
295
|
+
// mark as stale immediately
|
|
296
|
+
updates.stale = now;
|
|
297
|
+
if (durations.expire !== undefined) {
|
|
298
|
+
updates.expired = now + durations.expire * 1000; // Convert seconds to ms
|
|
299
|
+
}
|
|
300
|
+
globalTagsManifest.set(tag, updates);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Update expired field for immediate expiration (default behavior)
|
|
304
|
+
globalTagsManifest.set(tag, { ...existingEntry, expired: now });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/data-cache/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-based Data Cache Handler for "use cache" directive
|
|
3
|
+
* For production use with distributed Next.js deployments
|
|
4
|
+
*
|
|
5
|
+
* This handler stores cache entries in Redis, allowing multiple
|
|
6
|
+
* Next.js instances to share the same cache.
|
|
7
|
+
*/
|
|
8
|
+
import type { DataCacheHandler } from "./types.js";
|
|
9
|
+
export interface RedisDataCacheHandlerOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Redis client instance (ioredis or node-redis)
|
|
12
|
+
*/
|
|
13
|
+
redis: RedisClient;
|
|
14
|
+
/**
|
|
15
|
+
* Key prefix for all cache entries
|
|
16
|
+
* @default "nextjs:data-cache:"
|
|
17
|
+
*/
|
|
18
|
+
keyPrefix?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Key prefix for tag manifest
|
|
21
|
+
* @default "nextjs:tags:"
|
|
22
|
+
*/
|
|
23
|
+
tagPrefix?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Enable debug logging
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Default TTL for cache entries in seconds
|
|
31
|
+
* Used if entry doesn't specify expiration
|
|
32
|
+
* @default 86400 (24 hours)
|
|
33
|
+
*/
|
|
34
|
+
defaultTTL?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Redis client interface (compatible with ioredis and node-redis)
|
|
38
|
+
*/
|
|
39
|
+
export interface RedisClient {
|
|
40
|
+
get(key: string): Promise<string | null>;
|
|
41
|
+
set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
|
|
42
|
+
del(...keys: string[]): Promise<number>;
|
|
43
|
+
exists(...keys: string[]): Promise<number>;
|
|
44
|
+
ttl(key: string): Promise<number>;
|
|
45
|
+
hGet(key: string, field: string): Promise<string | null>;
|
|
46
|
+
hSet(key: string, field: string, value: string): Promise<unknown>;
|
|
47
|
+
hGetAll(key: string): Promise<Record<string, string>>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a Redis-based data cache handler for "use cache" directive
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* // redis-cache-handler.mjs
|
|
55
|
+
* import { createClient } from 'redis';
|
|
56
|
+
* import { createRedisDataCacheHandler } from '@mrjasonroy/better-nextjs-cache-handler/data-cache';
|
|
57
|
+
*
|
|
58
|
+
* const redis = createClient({
|
|
59
|
+
* url: process.env.REDIS_URL
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* await redis.connect();
|
|
63
|
+
*
|
|
64
|
+
* export default createRedisDataCacheHandler({
|
|
65
|
+
* redis,
|
|
66
|
+
* keyPrefix: 'myapp:cache:',
|
|
67
|
+
* debug: process.env.NODE_ENV === 'development'
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* Then in next.config.js:
|
|
72
|
+
* ```javascript
|
|
73
|
+
* module.exports = {
|
|
74
|
+
* cacheComponents: true,
|
|
75
|
+
* cacheHandlers: {
|
|
76
|
+
* default: require.resolve('./redis-cache-handler.mjs'),
|
|
77
|
+
* remote: require.resolve('./redis-cache-handler.mjs') // Same handler for remote
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function createRedisDataCacheHandler(options: RedisDataCacheHandlerOptions): DataCacheHandler;
|
|
83
|
+
//# sourceMappingURL=redis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/data-cache/redis.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAkB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,KAAK,EAAE,WAAW,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAmDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,gBAAgB,CAwMlB"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-based Data Cache Handler for "use cache" directive
|
|
3
|
+
* For production use with distributed Next.js deployments
|
|
4
|
+
*
|
|
5
|
+
* This handler stores cache entries in Redis, allowing multiple
|
|
6
|
+
* Next.js instances to share the same cache.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Convert ReadableStream to Buffer
|
|
10
|
+
*/
|
|
11
|
+
async function streamToBuffer(stream) {
|
|
12
|
+
const reader = stream.getReader();
|
|
13
|
+
const chunks = [];
|
|
14
|
+
try {
|
|
15
|
+
while (true) {
|
|
16
|
+
const { done, value } = await reader.read();
|
|
17
|
+
if (done)
|
|
18
|
+
break;
|
|
19
|
+
chunks.push(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
reader.releaseLock();
|
|
24
|
+
}
|
|
25
|
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
26
|
+
const result = new Uint8Array(totalLength);
|
|
27
|
+
let offset = 0;
|
|
28
|
+
for (const chunk of chunks) {
|
|
29
|
+
result.set(chunk, offset);
|
|
30
|
+
offset += chunk.length;
|
|
31
|
+
}
|
|
32
|
+
return Buffer.from(result);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert Buffer to ReadableStream
|
|
36
|
+
*/
|
|
37
|
+
function bufferToStream(buffer) {
|
|
38
|
+
return new ReadableStream({
|
|
39
|
+
start(controller) {
|
|
40
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
41
|
+
controller.close();
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a Redis-based data cache handler for "use cache" directive
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* // redis-cache-handler.mjs
|
|
51
|
+
* import { createClient } from 'redis';
|
|
52
|
+
* import { createRedisDataCacheHandler } from '@mrjasonroy/better-nextjs-cache-handler/data-cache';
|
|
53
|
+
*
|
|
54
|
+
* const redis = createClient({
|
|
55
|
+
* url: process.env.REDIS_URL
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* await redis.connect();
|
|
59
|
+
*
|
|
60
|
+
* export default createRedisDataCacheHandler({
|
|
61
|
+
* redis,
|
|
62
|
+
* keyPrefix: 'myapp:cache:',
|
|
63
|
+
* debug: process.env.NODE_ENV === 'development'
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* Then in next.config.js:
|
|
68
|
+
* ```javascript
|
|
69
|
+
* module.exports = {
|
|
70
|
+
* cacheComponents: true,
|
|
71
|
+
* cacheHandlers: {
|
|
72
|
+
* default: require.resolve('./redis-cache-handler.mjs'),
|
|
73
|
+
* remote: require.resolve('./redis-cache-handler.mjs') // Same handler for remote
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function createRedisDataCacheHandler(options) {
|
|
79
|
+
const { redis, keyPrefix = "nextjs:data-cache:", tagPrefix = "nextjs:tags:", defaultTTL = 86400, debug = false, } = options;
|
|
80
|
+
const log = debug ? console.debug.bind(console, "[RedisDataCache]:") : undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Get cache key with prefix
|
|
83
|
+
*/
|
|
84
|
+
function getCacheKey(cacheKey) {
|
|
85
|
+
return `${keyPrefix}${cacheKey}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get tag key with prefix
|
|
89
|
+
*/
|
|
90
|
+
function getTagKey(tag) {
|
|
91
|
+
return `${tagPrefix}${tag}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Serialize cache entry for Redis storage
|
|
95
|
+
*/
|
|
96
|
+
async function serializeEntry(entry) {
|
|
97
|
+
const buffer = await streamToBuffer(entry.value);
|
|
98
|
+
return {
|
|
99
|
+
value: buffer.toString("base64"),
|
|
100
|
+
tags: entry.tags,
|
|
101
|
+
stale: entry.stale,
|
|
102
|
+
timestamp: entry.timestamp,
|
|
103
|
+
expire: entry.expire,
|
|
104
|
+
revalidate: entry.revalidate,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Deserialize cache entry from Redis
|
|
109
|
+
*/
|
|
110
|
+
function deserializeEntry(data) {
|
|
111
|
+
const buffer = Buffer.from(data.value, "base64");
|
|
112
|
+
return {
|
|
113
|
+
value: bufferToStream(buffer),
|
|
114
|
+
tags: data.tags,
|
|
115
|
+
stale: data.stale,
|
|
116
|
+
timestamp: data.timestamp,
|
|
117
|
+
expire: data.expire,
|
|
118
|
+
revalidate: data.revalidate,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
async get(cacheKey, _softTags) {
|
|
123
|
+
const key = getCacheKey(cacheKey);
|
|
124
|
+
try {
|
|
125
|
+
const data = await redis.get(key);
|
|
126
|
+
if (!data) {
|
|
127
|
+
log?.("get", cacheKey, "not found");
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const entry = deserializeEntry(JSON.parse(data));
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
// Check expiration
|
|
133
|
+
if (now > entry.timestamp + entry.revalidate * 1000) {
|
|
134
|
+
log?.("get", cacheKey, "expired");
|
|
135
|
+
await redis.del(key);
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
// Check tag expiration
|
|
139
|
+
let revalidate = entry.revalidate;
|
|
140
|
+
for (const tag of entry.tags) {
|
|
141
|
+
const tagData = await redis.hGetAll(getTagKey(tag));
|
|
142
|
+
if (tagData.expired) {
|
|
143
|
+
const expired = Number.parseInt(tagData.expired, 10);
|
|
144
|
+
if (expired > entry.timestamp) {
|
|
145
|
+
log?.("get", cacheKey, "had expired tag", tag);
|
|
146
|
+
await redis.del(key);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (tagData.stale) {
|
|
151
|
+
const stale = Number.parseInt(tagData.stale, 10);
|
|
152
|
+
if (stale > entry.timestamp) {
|
|
153
|
+
log?.("get", cacheKey, "had stale tag", tag);
|
|
154
|
+
revalidate = -1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Tee the stream
|
|
159
|
+
const [returnStream, newSaved] = entry.value.tee();
|
|
160
|
+
entry.value = newSaved;
|
|
161
|
+
log?.("get", cacheKey, "found", {
|
|
162
|
+
tags: entry.tags,
|
|
163
|
+
timestamp: entry.timestamp,
|
|
164
|
+
revalidate,
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
...entry,
|
|
168
|
+
revalidate,
|
|
169
|
+
value: returnStream,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
log?.("get", cacheKey, "error", error);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
async set(cacheKey, pendingEntry) {
|
|
178
|
+
const key = getCacheKey(cacheKey);
|
|
179
|
+
try {
|
|
180
|
+
log?.("set", cacheKey, "start");
|
|
181
|
+
const entry = await pendingEntry;
|
|
182
|
+
const serialized = await serializeEntry(entry);
|
|
183
|
+
// Calculate TTL (use expire time or default)
|
|
184
|
+
const ttl = entry.expire < 4294967294 ? entry.expire : defaultTTL;
|
|
185
|
+
// Store in Redis with TTL
|
|
186
|
+
await redis.set(key, JSON.stringify(serialized), "EX", Math.ceil(ttl));
|
|
187
|
+
log?.("set", cacheKey, "done", { ttl });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
log?.("set", cacheKey, "failed", error);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async refreshTags() {
|
|
194
|
+
// For Redis, tags are stored in a hash and don't need periodic refresh
|
|
195
|
+
// unless you're syncing with an external tags service
|
|
196
|
+
log?.("refreshTags", "no-op for Redis cache");
|
|
197
|
+
},
|
|
198
|
+
async getExpiration(tags) {
|
|
199
|
+
try {
|
|
200
|
+
const expirations = await Promise.all(tags.map(async (tag) => {
|
|
201
|
+
const data = await redis.hGet(getTagKey(tag), "expired");
|
|
202
|
+
return data ? Number.parseInt(data, 10) : 0;
|
|
203
|
+
}));
|
|
204
|
+
const expiration = Math.max(...expirations, 0);
|
|
205
|
+
log?.("getExpiration", { tags, expiration });
|
|
206
|
+
return expiration;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
log?.("getExpiration", "error", error);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
async updateTags(tags, durations) {
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
try {
|
|
216
|
+
log?.("updateTags", { tags, timestamp: now, durations });
|
|
217
|
+
await Promise.all(tags.map(async (tag) => {
|
|
218
|
+
const key = getTagKey(tag);
|
|
219
|
+
if (durations) {
|
|
220
|
+
// Mark as stale immediately
|
|
221
|
+
await redis.hSet(key, "stale", now.toString());
|
|
222
|
+
// Set expiration if provided
|
|
223
|
+
if (durations.expire !== undefined) {
|
|
224
|
+
const expired = now + durations.expire * 1000;
|
|
225
|
+
await redis.hSet(key, "expired", expired.toString());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Immediate expiration (default behavior)
|
|
230
|
+
await redis.hSet(key, "expired", now.toString());
|
|
231
|
+
}
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
log?.("updateTags", "error", error);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/data-cache/redis.ts"],"names":[],"mappings":"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"}
|