@leejpsd/nextjs-cache-handler 0.2.0 → 0.3.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/CHANGELOG.md +59 -2
- package/README.md +47 -25
- package/dist/cache-components/index.cjs +235 -46
- package/dist/cache-components/index.cjs.map +1 -1
- package/dist/cache-components/index.d.cts +1 -1
- package/dist/cache-components/index.d.ts +1 -1
- package/dist/cache-components/index.js +227 -46
- package/dist/cache-components/index.js.map +1 -1
- package/dist/incremental/index.cjs +231 -41
- package/dist/incremental/index.cjs.map +1 -1
- package/dist/incremental/index.d.cts +42 -3
- package/dist/incremental/index.d.ts +42 -3
- package/dist/incremental/index.js +223 -41
- package/dist/incremental/index.js.map +1 -1
- package/dist/index.cjs +313 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +305 -70
- package/dist/index.js.map +1 -1
- package/dist/ops/index.d.cts +1 -1
- package/dist/ops/index.d.ts +1 -1
- package/dist/otel/index.cjs +72 -0
- package/dist/otel/index.cjs.map +1 -0
- package/dist/otel/index.d.cts +72 -0
- package/dist/otel/index.d.ts +72 -0
- package/dist/otel/index.js +64 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/shared/client/adapter-ioredis.cjs +49 -10
- package/dist/shared/client/adapter-ioredis.cjs.map +1 -1
- package/dist/shared/client/adapter-ioredis.d.cts +5 -2
- package/dist/shared/client/adapter-ioredis.d.ts +5 -2
- package/dist/shared/client/adapter-ioredis.js +43 -11
- package/dist/shared/client/adapter-ioredis.js.map +1 -1
- package/dist/shared/client/adapter-redis.cjs +22 -7
- package/dist/shared/client/adapter-redis.cjs.map +1 -1
- package/dist/shared/client/adapter-redis.d.cts +1 -1
- package/dist/shared/client/adapter-redis.d.ts +1 -1
- package/dist/shared/client/adapter-redis.js +16 -7
- package/dist/shared/client/adapter-redis.js.map +1 -1
- package/dist/{types-Dq1q6qXd.d.cts → types-Bl-rkLKX.d.cts} +37 -1
- package/dist/{types-Dq1q6qXd.d.ts → types-Bl-rkLKX.d.ts} +37 -1
- package/package.json +20 -3
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var module$1 = require('module');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var util = require('util');
|
|
6
|
+
var zlib = require('zlib');
|
|
3
7
|
var crypto = require('crypto');
|
|
4
8
|
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
+
|
|
5
13
|
var __defProp = Object.defineProperty;
|
|
6
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
-
}) : x)(function(x) {
|
|
10
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
-
});
|
|
13
15
|
var __esm = (fn, res) => function __init() {
|
|
14
16
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
17
|
};
|
|
@@ -18,6 +20,32 @@ var __export = (target, all) => {
|
|
|
18
20
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
21
|
};
|
|
20
22
|
|
|
23
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
24
|
+
var getImportMetaUrl, importMetaUrl;
|
|
25
|
+
var init_cjs_shims = __esm({
|
|
26
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
27
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
28
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
function nodeRequire(id) {
|
|
32
|
+
try {
|
|
33
|
+
return packageRequire(id);
|
|
34
|
+
} catch {
|
|
35
|
+
return cwdRequire(id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
var packageRequire, cwdRequire;
|
|
39
|
+
var init_node_require = __esm({
|
|
40
|
+
"src/shared/node-require.ts"() {
|
|
41
|
+
init_cjs_shims();
|
|
42
|
+
packageRequire = module$1.createRequire(importMetaUrl);
|
|
43
|
+
cwdRequire = module$1.createRequire(
|
|
44
|
+
path__default.default.join(process.cwd(), "__resolve-anchor__.js")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
21
49
|
// src/shared/client/adapter-redis.ts
|
|
22
50
|
var adapter_redis_exports = {};
|
|
23
51
|
__export(adapter_redis_exports, {
|
|
@@ -28,7 +56,7 @@ function adaptRedisV5(client) {
|
|
|
28
56
|
return client;
|
|
29
57
|
}
|
|
30
58
|
function createRedisV5Client(config) {
|
|
31
|
-
const { createClient } =
|
|
59
|
+
const { createClient } = nodeRequire("redis");
|
|
32
60
|
const useTls = config.tls ?? config.url.startsWith("rediss://");
|
|
33
61
|
const opts = {
|
|
34
62
|
url: config.url,
|
|
@@ -43,6 +71,8 @@ function createRedisV5Client(config) {
|
|
|
43
71
|
}
|
|
44
72
|
var init_adapter_redis = __esm({
|
|
45
73
|
"src/shared/client/adapter-redis.ts"() {
|
|
74
|
+
init_cjs_shims();
|
|
75
|
+
init_node_require();
|
|
46
76
|
}
|
|
47
77
|
});
|
|
48
78
|
|
|
@@ -52,7 +82,8 @@ __export(adapter_ioredis_exports, {
|
|
|
52
82
|
adaptCluster: () => adaptCluster,
|
|
53
83
|
adaptIoredis: () => adaptIoredis,
|
|
54
84
|
createIoredisClient: () => createIoredisClient,
|
|
55
|
-
createIoredisCluster: () => createIoredisCluster
|
|
85
|
+
createIoredisCluster: () => createIoredisCluster,
|
|
86
|
+
createIoredisSentinel: () => createIoredisSentinel
|
|
56
87
|
});
|
|
57
88
|
function adapt(client, isClusterClient = false) {
|
|
58
89
|
return {
|
|
@@ -135,6 +166,9 @@ function adapt(client, isClusterClient = false) {
|
|
|
135
166
|
on: (event, listener) => {
|
|
136
167
|
client.on(event, listener);
|
|
137
168
|
return client;
|
|
169
|
+
},
|
|
170
|
+
dispose: () => {
|
|
171
|
+
client.disconnect();
|
|
138
172
|
}
|
|
139
173
|
};
|
|
140
174
|
}
|
|
@@ -145,18 +179,36 @@ function adaptCluster(cluster) {
|
|
|
145
179
|
return adapt(cluster, true);
|
|
146
180
|
}
|
|
147
181
|
function createIoredisClient(config) {
|
|
148
|
-
const IORedis =
|
|
182
|
+
const IORedis = nodeRequire("ioredis");
|
|
149
183
|
const Ctor = IORedis.default ?? IORedis;
|
|
150
184
|
return new Ctor(config.url, {
|
|
151
185
|
password: config.password,
|
|
152
186
|
connectTimeout: config.connectTimeout ?? 1500,
|
|
153
187
|
tls: config.tls ? {} : void 0,
|
|
154
188
|
lazyConnect: true,
|
|
155
|
-
maxRetriesPerRequest: 1
|
|
189
|
+
maxRetriesPerRequest: 1,
|
|
190
|
+
// Batch same-tick commands into one RTT (matches redis@5's default
|
|
191
|
+
// behavior) — makes Promise.all fan-outs a single pipeline.
|
|
192
|
+
enableAutoPipelining: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function createIoredisSentinel(config) {
|
|
196
|
+
const IORedis = nodeRequire("ioredis");
|
|
197
|
+
const Ctor = IORedis.default ?? IORedis;
|
|
198
|
+
return new Ctor({
|
|
199
|
+
sentinels: config.sentinels,
|
|
200
|
+
name: config.name,
|
|
201
|
+
password: config.password,
|
|
202
|
+
sentinelPassword: config.sentinelPassword,
|
|
203
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
204
|
+
tls: config.tls ? {} : void 0,
|
|
205
|
+
lazyConnect: true,
|
|
206
|
+
maxRetriesPerRequest: 1,
|
|
207
|
+
enableAutoPipelining: true
|
|
156
208
|
});
|
|
157
209
|
}
|
|
158
210
|
function createIoredisCluster(config) {
|
|
159
|
-
const IORedis =
|
|
211
|
+
const IORedis = nodeRequire("ioredis");
|
|
160
212
|
const ClusterCtor = IORedis.Cluster ?? IORedis.default.Cluster;
|
|
161
213
|
return new ClusterCtor(config.nodes, {
|
|
162
214
|
redisOptions: {
|
|
@@ -164,15 +216,29 @@ function createIoredisCluster(config) {
|
|
|
164
216
|
tls: config.tls ? {} : void 0
|
|
165
217
|
},
|
|
166
218
|
enableReadyCheck: true,
|
|
167
|
-
lazyConnect: true
|
|
219
|
+
lazyConnect: true,
|
|
220
|
+
// Same-slot commands issued in the same tick share one pipeline.
|
|
221
|
+
enableAutoPipelining: true
|
|
168
222
|
});
|
|
169
223
|
}
|
|
170
224
|
var init_adapter_ioredis = __esm({
|
|
171
225
|
"src/shared/client/adapter-ioredis.ts"() {
|
|
226
|
+
init_cjs_shims();
|
|
227
|
+
init_node_require();
|
|
172
228
|
}
|
|
173
229
|
});
|
|
174
230
|
|
|
231
|
+
// src/index.ts
|
|
232
|
+
init_cjs_shims();
|
|
233
|
+
|
|
234
|
+
// src/cache-components/index.ts
|
|
235
|
+
init_cjs_shims();
|
|
236
|
+
|
|
237
|
+
// src/cache-components/handler.ts
|
|
238
|
+
init_cjs_shims();
|
|
239
|
+
|
|
175
240
|
// src/shared/abort.ts
|
|
241
|
+
init_cjs_shims();
|
|
176
242
|
var CacheTimeoutError = class extends Error {
|
|
177
243
|
constructor(opName, ms) {
|
|
178
244
|
super(`[@leejpsd/nextjs-cache-handler] ${opName} timed out after ${ms}ms`);
|
|
@@ -204,7 +270,41 @@ async function withAbortSignal(opName, ms, fn) {
|
|
|
204
270
|
}
|
|
205
271
|
}
|
|
206
272
|
|
|
273
|
+
// src/shared/compress.ts
|
|
274
|
+
init_cjs_shims();
|
|
275
|
+
var gzipAsync = util.promisify(zlib.gzip);
|
|
276
|
+
var gunzipAsync = util.promisify(zlib.gunzip);
|
|
277
|
+
var brotliCompressAsync = util.promisify(zlib.brotliCompress);
|
|
278
|
+
var brotliDecompressAsync = util.promisify(zlib.brotliDecompress);
|
|
279
|
+
var GZIP_MARKER = "__ncgz__:";
|
|
280
|
+
var BROTLI_MARKER = "__ncbr__:";
|
|
281
|
+
var MIN_COMPRESS_BYTES = 1024;
|
|
282
|
+
var BROTLI_OPTS = {
|
|
283
|
+
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 }
|
|
284
|
+
};
|
|
285
|
+
async function compressValue(value, algo) {
|
|
286
|
+
if (!algo || Buffer.byteLength(value) < MIN_COMPRESS_BYTES) return value;
|
|
287
|
+
const input = Buffer.from(value);
|
|
288
|
+
if (algo === "gzip") {
|
|
289
|
+
return GZIP_MARKER + (await gzipAsync(input)).toString("base64");
|
|
290
|
+
}
|
|
291
|
+
return BROTLI_MARKER + (await brotliCompressAsync(input, BROTLI_OPTS)).toString("base64");
|
|
292
|
+
}
|
|
293
|
+
async function decompressValue(value) {
|
|
294
|
+
if (value === null) return null;
|
|
295
|
+
if (value.startsWith(GZIP_MARKER)) {
|
|
296
|
+
const buf = Buffer.from(value.slice(GZIP_MARKER.length), "base64");
|
|
297
|
+
return (await gunzipAsync(buf)).toString("utf8");
|
|
298
|
+
}
|
|
299
|
+
if (value.startsWith(BROTLI_MARKER)) {
|
|
300
|
+
const buf = Buffer.from(value.slice(BROTLI_MARKER.length), "base64");
|
|
301
|
+
return (await brotliDecompressAsync(buf)).toString("utf8");
|
|
302
|
+
}
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
207
306
|
// src/shared/client/index.ts
|
|
307
|
+
init_cjs_shims();
|
|
208
308
|
async function buildClient(source) {
|
|
209
309
|
if (typeof source === "function") {
|
|
210
310
|
return await source();
|
|
@@ -222,6 +322,10 @@ async function buildClient(source) {
|
|
|
222
322
|
const { adaptCluster: adaptCluster2, createIoredisCluster: createIoredisCluster2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
223
323
|
return adaptCluster2(createIoredisCluster2(source));
|
|
224
324
|
}
|
|
325
|
+
case "sentinel": {
|
|
326
|
+
const { adaptIoredis: adaptIoredis2, createIoredisSentinel: createIoredisSentinel2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
327
|
+
return adaptIoredis2(createIoredisSentinel2(source));
|
|
328
|
+
}
|
|
225
329
|
default: {
|
|
226
330
|
const exhaustive = source;
|
|
227
331
|
throw new Error(
|
|
@@ -231,28 +335,45 @@ async function buildClient(source) {
|
|
|
231
335
|
}
|
|
232
336
|
}
|
|
233
337
|
var ConnectionManager = class {
|
|
234
|
-
constructor(source, onError) {
|
|
338
|
+
constructor(source, onError, retry = {}) {
|
|
235
339
|
this.source = source;
|
|
236
340
|
this.onError = onError;
|
|
341
|
+
this.baseCooldownMs = retry.baseCooldownMs ?? 1e3;
|
|
342
|
+
this.maxCooldownMs = retry.maxCooldownMs ?? 3e4;
|
|
237
343
|
}
|
|
238
344
|
source;
|
|
239
345
|
onError;
|
|
240
346
|
client = null;
|
|
241
347
|
connecting = null;
|
|
242
|
-
|
|
348
|
+
consecutiveFailures = 0;
|
|
349
|
+
lastFailureAt = 0;
|
|
350
|
+
baseCooldownMs;
|
|
351
|
+
maxCooldownMs;
|
|
352
|
+
cooldownMs() {
|
|
353
|
+
if (this.consecutiveFailures === 0) return 0;
|
|
354
|
+
return Math.min(
|
|
355
|
+
this.maxCooldownMs,
|
|
356
|
+
this.baseCooldownMs * 2 ** (this.consecutiveFailures - 1)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
243
359
|
async getOrConnect() {
|
|
244
360
|
if (this.client?.isOpen) return this.client;
|
|
245
|
-
if (this.failed) return null;
|
|
246
361
|
if (this.connecting) return this.connecting;
|
|
362
|
+
if (Date.now() - this.lastFailureAt < this.cooldownMs()) return null;
|
|
363
|
+
const stale = this.client;
|
|
364
|
+
this.client = null;
|
|
247
365
|
this.connecting = (async () => {
|
|
366
|
+
disposeQuietly(stale);
|
|
248
367
|
try {
|
|
249
368
|
const c = await buildClient(this.source);
|
|
250
369
|
c.on("error", (err) => this.onError(err));
|
|
251
370
|
if (!c.isOpen) await c.connect();
|
|
252
371
|
this.client = c;
|
|
372
|
+
this.consecutiveFailures = 0;
|
|
253
373
|
return c;
|
|
254
374
|
} catch (err) {
|
|
255
|
-
this.
|
|
375
|
+
this.consecutiveFailures += 1;
|
|
376
|
+
this.lastFailureAt = Date.now();
|
|
256
377
|
this.onError(err);
|
|
257
378
|
return null;
|
|
258
379
|
} finally {
|
|
@@ -262,13 +383,24 @@ var ConnectionManager = class {
|
|
|
262
383
|
return this.connecting;
|
|
263
384
|
}
|
|
264
385
|
reset() {
|
|
386
|
+
disposeQuietly(this.client);
|
|
265
387
|
this.client = null;
|
|
266
388
|
this.connecting = null;
|
|
267
|
-
this.
|
|
389
|
+
this.consecutiveFailures = 0;
|
|
390
|
+
this.lastFailureAt = 0;
|
|
268
391
|
}
|
|
269
392
|
};
|
|
393
|
+
function disposeQuietly(client) {
|
|
394
|
+
if (!client) return;
|
|
395
|
+
const c = client;
|
|
396
|
+
try {
|
|
397
|
+
(c.dispose ?? c.destroy ?? c.disconnect)?.call(c);
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
270
401
|
|
|
271
402
|
// src/shared/logger.ts
|
|
403
|
+
init_cjs_shims();
|
|
272
404
|
var defaultLogger = {
|
|
273
405
|
debug: () => {
|
|
274
406
|
},
|
|
@@ -284,6 +416,9 @@ var defaultLogger = {
|
|
|
284
416
|
}
|
|
285
417
|
};
|
|
286
418
|
|
|
419
|
+
// src/shared/lua/index.ts
|
|
420
|
+
init_cjs_shims();
|
|
421
|
+
|
|
287
422
|
// src/shared/lua/set-with-tags.lua
|
|
288
423
|
var set_with_tags_default = `-- set-with-tags.lua
|
|
289
424
|
--
|
|
@@ -387,13 +522,19 @@ async function execLuaScript(client, name, keys, args) {
|
|
|
387
522
|
}
|
|
388
523
|
|
|
389
524
|
// src/shared/memory-fallback.ts
|
|
525
|
+
init_cjs_shims();
|
|
390
526
|
var MAX_TIMER_MS = 2147483647;
|
|
391
527
|
function clampTimerMs(ms) {
|
|
392
528
|
return Math.min(Math.max(0, ms), MAX_TIMER_MS);
|
|
393
529
|
}
|
|
530
|
+
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
394
531
|
var MemoryStore = class {
|
|
395
532
|
kv = /* @__PURE__ */ new Map();
|
|
396
533
|
timers = /* @__PURE__ */ new Map();
|
|
534
|
+
maxEntries;
|
|
535
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
536
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
537
|
+
}
|
|
397
538
|
set(key, value, ttlSeconds) {
|
|
398
539
|
this.delete(key);
|
|
399
540
|
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
@@ -403,6 +544,10 @@ var MemoryStore = class {
|
|
|
403
544
|
timer.unref();
|
|
404
545
|
}
|
|
405
546
|
this.timers.set(key, timer);
|
|
547
|
+
while (this.kv.size > this.maxEntries) {
|
|
548
|
+
const oldest = this.kv.keys().next().value;
|
|
549
|
+
this.delete(oldest);
|
|
550
|
+
}
|
|
406
551
|
}
|
|
407
552
|
get(key) {
|
|
408
553
|
const entry = this.kv.get(key);
|
|
@@ -411,6 +556,8 @@ var MemoryStore = class {
|
|
|
411
556
|
this.delete(key);
|
|
412
557
|
return null;
|
|
413
558
|
}
|
|
559
|
+
this.kv.delete(key);
|
|
560
|
+
this.kv.set(key, entry);
|
|
414
561
|
return entry.value;
|
|
415
562
|
}
|
|
416
563
|
delete(key) {
|
|
@@ -445,13 +592,19 @@ var MemorySetStore = class {
|
|
|
445
592
|
sets = /* @__PURE__ */ new Map();
|
|
446
593
|
expiresAt = /* @__PURE__ */ new Map();
|
|
447
594
|
timers = /* @__PURE__ */ new Map();
|
|
595
|
+
maxSets;
|
|
596
|
+
constructor(maxSets = DEFAULT_MAX_ENTRIES) {
|
|
597
|
+
this.maxSets = Math.max(1, maxSets);
|
|
598
|
+
}
|
|
448
599
|
sAdd(key, member) {
|
|
449
600
|
const members = Array.isArray(member) ? member : [member];
|
|
450
601
|
let set = this.sets.get(key);
|
|
451
602
|
if (!set) {
|
|
452
603
|
set = /* @__PURE__ */ new Set();
|
|
453
|
-
|
|
604
|
+
} else {
|
|
605
|
+
this.sets.delete(key);
|
|
454
606
|
}
|
|
607
|
+
this.sets.set(key, set);
|
|
455
608
|
let added = 0;
|
|
456
609
|
for (const m of members) {
|
|
457
610
|
if (!set.has(m)) {
|
|
@@ -459,6 +612,10 @@ var MemorySetStore = class {
|
|
|
459
612
|
added += 1;
|
|
460
613
|
}
|
|
461
614
|
}
|
|
615
|
+
while (this.sets.size > this.maxSets) {
|
|
616
|
+
const oldest = this.sets.keys().next().value;
|
|
617
|
+
this.delete(oldest);
|
|
618
|
+
}
|
|
462
619
|
return added;
|
|
463
620
|
}
|
|
464
621
|
sMembers(key) {
|
|
@@ -511,6 +668,7 @@ var MemorySetStore = class {
|
|
|
511
668
|
};
|
|
512
669
|
|
|
513
670
|
// src/shared/metrics.ts
|
|
671
|
+
init_cjs_shims();
|
|
514
672
|
var counters = /* @__PURE__ */ new Map();
|
|
515
673
|
function bump(event) {
|
|
516
674
|
const existing = counters.get(event.type);
|
|
@@ -550,6 +708,7 @@ function getMetricSnapshot() {
|
|
|
550
708
|
}
|
|
551
709
|
|
|
552
710
|
// src/shared/namespace.ts
|
|
711
|
+
init_cjs_shims();
|
|
553
712
|
var FALLBACK = "unversioned";
|
|
554
713
|
function resolveBuildNamespace(override) {
|
|
555
714
|
if (override !== void 0) {
|
|
@@ -563,6 +722,7 @@ function buildKey(prefix, namespace, rest, hashTag = false) {
|
|
|
563
722
|
}
|
|
564
723
|
|
|
565
724
|
// src/shared/build-phase.ts
|
|
725
|
+
init_cjs_shims();
|
|
566
726
|
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
567
727
|
function isBuildPhase() {
|
|
568
728
|
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
|
|
@@ -575,6 +735,7 @@ function shouldUseRedis(opts) {
|
|
|
575
735
|
}
|
|
576
736
|
|
|
577
737
|
// src/cache-components/serialize.ts
|
|
738
|
+
init_cjs_shims();
|
|
578
739
|
async function readStreamFully(stream) {
|
|
579
740
|
const reader = stream.getReader();
|
|
580
741
|
const chunks = [];
|
|
@@ -617,6 +778,7 @@ function decodeEnvelope(raw) {
|
|
|
617
778
|
}
|
|
618
779
|
|
|
619
780
|
// src/cache-components/swr.ts
|
|
781
|
+
init_cjs_shims();
|
|
620
782
|
var SECOND_MS = 1e3;
|
|
621
783
|
function partitionEntry(entry, now = Date.now()) {
|
|
622
784
|
const ageMs = now - entry.timestamp;
|
|
@@ -675,8 +837,8 @@ function init(opts) {
|
|
|
675
837
|
logger,
|
|
676
838
|
emit,
|
|
677
839
|
conn,
|
|
678
|
-
memEntries: new MemoryStore(),
|
|
679
|
-
memTags: new MemorySetStore(),
|
|
840
|
+
memEntries: new MemoryStore(opts.memoryMaxEntries),
|
|
841
|
+
memTags: new MemorySetStore(opts.memoryMaxEntries),
|
|
680
842
|
memTagExp: /* @__PURE__ */ new Map(),
|
|
681
843
|
localTagTimestamps: /* @__PURE__ */ new Map(),
|
|
682
844
|
resolveNs: () => resolveBuildNamespace(opts.buildNamespace),
|
|
@@ -793,6 +955,12 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
793
955
|
state.emit({ type: "cache.miss", ms: Date.now() - start });
|
|
794
956
|
return void 0;
|
|
795
957
|
}
|
|
958
|
+
try {
|
|
959
|
+
raw = await decompressValue(raw);
|
|
960
|
+
} catch {
|
|
961
|
+
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
796
964
|
envelope = decodeEnvelope(raw);
|
|
797
965
|
if (!envelope) {
|
|
798
966
|
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
@@ -921,10 +1089,14 @@ async function setImpl(state, cacheKey, pendingEntry) {
|
|
|
921
1089
|
}
|
|
922
1090
|
return;
|
|
923
1091
|
}
|
|
1092
|
+
const wirePayload = await compressValue(
|
|
1093
|
+
payload,
|
|
1094
|
+
state.opts.rest.compression
|
|
1095
|
+
);
|
|
924
1096
|
if (entry.tags.length === 0) {
|
|
925
|
-
await client.set(eKey,
|
|
1097
|
+
await client.set(eKey, wirePayload, { EX: ttl });
|
|
926
1098
|
} else {
|
|
927
|
-
await luaSetWithTags(client, state, eKey,
|
|
1099
|
+
await luaSetWithTags(client, state, eKey, wirePayload, ttl, entry.tags);
|
|
928
1100
|
}
|
|
929
1101
|
}
|
|
930
1102
|
);
|
|
@@ -960,28 +1132,36 @@ async function refreshTagsImpl(state) {
|
|
|
960
1132
|
async () => {
|
|
961
1133
|
const client = await state.conn.getOrConnect();
|
|
962
1134
|
if (!client) return;
|
|
963
|
-
const
|
|
964
|
-
const
|
|
1135
|
+
const ns = state.opts.hashTag ? `{${state.resolveNs()}}` : state.resolveNs();
|
|
1136
|
+
const pattern = `${escapeMatch(
|
|
1137
|
+
`${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`
|
|
1138
|
+
)}*`;
|
|
1139
|
+
const scanStartMs = Date.now();
|
|
1140
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
965
1141
|
for await (const chunk of client.scanIterator({
|
|
966
1142
|
MATCH: pattern,
|
|
967
1143
|
COUNT: 100
|
|
968
1144
|
})) {
|
|
969
1145
|
const keys = Array.isArray(chunk) ? chunk.map(String) : [String(chunk)];
|
|
970
|
-
|
|
971
|
-
|
|
1146
|
+
if (keys.length === 0) continue;
|
|
1147
|
+
const values = await client.mGet(keys);
|
|
1148
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
1149
|
+
const v = values[i];
|
|
1150
|
+
if (!v) continue;
|
|
1151
|
+
const ts = Number.parseInt(v, 10);
|
|
1152
|
+
if (!Number.isFinite(ts)) continue;
|
|
1153
|
+
const key = keys[i];
|
|
1154
|
+
if (key === void 0) continue;
|
|
1155
|
+
const tag = stripExpPrefix(state, key);
|
|
1156
|
+
if (tag) fresh.set(tag, ts);
|
|
1157
|
+
}
|
|
972
1158
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
if (!v) continue;
|
|
978
|
-
const ts = Number.parseInt(v, 10);
|
|
979
|
-
if (!Number.isFinite(ts)) continue;
|
|
980
|
-
const key = seen[i];
|
|
981
|
-
if (key === void 0) continue;
|
|
982
|
-
const tag = stripExpPrefix(state, key);
|
|
983
|
-
if (tag) state.localTagTimestamps.set(tag, ts);
|
|
1159
|
+
for (const [tag, ts] of state.localTagTimestamps) {
|
|
1160
|
+
if (ts >= scanStartMs && (fresh.get(tag) ?? 0) < ts) {
|
|
1161
|
+
fresh.set(tag, ts);
|
|
1162
|
+
}
|
|
984
1163
|
}
|
|
1164
|
+
state.localTagTimestamps = fresh;
|
|
985
1165
|
}
|
|
986
1166
|
);
|
|
987
1167
|
} catch (err) {
|
|
@@ -990,6 +1170,19 @@ async function refreshTagsImpl(state) {
|
|
|
990
1170
|
});
|
|
991
1171
|
}
|
|
992
1172
|
}
|
|
1173
|
+
function escapeMatch(s) {
|
|
1174
|
+
return s.replace(/[\\*?[\]]/g, "\\$&");
|
|
1175
|
+
}
|
|
1176
|
+
var MAX_LOCAL_TAG_ENTRIES = 1e4;
|
|
1177
|
+
function pruneOldestByTimestamp(map, maxEntries) {
|
|
1178
|
+
if (map.size <= maxEntries) return;
|
|
1179
|
+
const byAge = [...map.entries()].sort((a, b) => a[1] - b[1]);
|
|
1180
|
+
const drop = map.size - maxEntries;
|
|
1181
|
+
for (let i = 0; i < drop; i += 1) {
|
|
1182
|
+
const entry = byAge[i];
|
|
1183
|
+
if (entry) map.delete(entry[0]);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
993
1186
|
function stripExpPrefix(state, fullKey) {
|
|
994
1187
|
const ns = state.resolveNs();
|
|
995
1188
|
const prefix = state.opts.hashTag ? `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}{${ns}}:` : `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`;
|
|
@@ -1041,6 +1234,7 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1041
1234
|
state.localTagTimestamps.set(t, now);
|
|
1042
1235
|
state.memTagExp.set(t, now);
|
|
1043
1236
|
}
|
|
1237
|
+
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
1044
1238
|
if (!gateRedis(state)) {
|
|
1045
1239
|
if (isHardExpire) {
|
|
1046
1240
|
for (const t of tags) {
|
|
@@ -1070,20 +1264,18 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1070
1264
|
}
|
|
1071
1265
|
return;
|
|
1072
1266
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1267
|
+
await Promise.all(
|
|
1268
|
+
tags.map(
|
|
1269
|
+
(t) => isHardExpire ? execLuaScript(
|
|
1076
1270
|
client,
|
|
1077
1271
|
"revalidateHard",
|
|
1078
1272
|
[tagKey(state, t), tagExpKey(state, t)],
|
|
1079
1273
|
[String(now), String(TAG_EXP_MARKER_TTL_SEC)]
|
|
1080
|
-
)
|
|
1081
|
-
} else {
|
|
1082
|
-
await client.set(tagExpKey(state, t), String(now), {
|
|
1274
|
+
) : client.set(tagExpKey(state, t), String(now), {
|
|
1083
1275
|
EX: TAG_EXP_MARKER_TTL_SEC
|
|
1084
|
-
})
|
|
1085
|
-
|
|
1086
|
-
|
|
1276
|
+
})
|
|
1277
|
+
)
|
|
1278
|
+
);
|
|
1087
1279
|
}
|
|
1088
1280
|
);
|
|
1089
1281
|
state.emit({
|
|
@@ -1113,7 +1305,14 @@ function createCacheComponentsHandler(opts) {
|
|
|
1113
1305
|
};
|
|
1114
1306
|
}
|
|
1115
1307
|
|
|
1308
|
+
// src/incremental/index.ts
|
|
1309
|
+
init_cjs_shims();
|
|
1310
|
+
|
|
1311
|
+
// src/incremental/handler.ts
|
|
1312
|
+
init_cjs_shims();
|
|
1313
|
+
|
|
1116
1314
|
// src/incremental/serialize.ts
|
|
1315
|
+
init_cjs_shims();
|
|
1117
1316
|
function isEnvelope(value) {
|
|
1118
1317
|
return typeof value === "object" && value !== null && "__type" in value && typeof value.__type === "string";
|
|
1119
1318
|
}
|
|
@@ -1195,7 +1394,7 @@ function init2(opts) {
|
|
|
1195
1394
|
logger,
|
|
1196
1395
|
emit,
|
|
1197
1396
|
conn,
|
|
1198
|
-
memEntries: new MemoryStore(),
|
|
1397
|
+
memEntries: new MemoryStore(opts.memoryMaxEntries),
|
|
1199
1398
|
memTagStates: /* @__PURE__ */ new Map(),
|
|
1200
1399
|
resolveNs: () => resolveBuildNamespace(opts.buildNamespace)
|
|
1201
1400
|
};
|
|
@@ -1254,7 +1453,7 @@ function normalizeTtlSeconds(data, ctx) {
|
|
|
1254
1453
|
return ONE_YEAR_SEC2;
|
|
1255
1454
|
}
|
|
1256
1455
|
}
|
|
1257
|
-
const revalidate = ctx?.cacheControl?.revalidate;
|
|
1456
|
+
const revalidate = ctx?.cacheControl?.revalidate ?? ctx?.revalidate;
|
|
1258
1457
|
if (revalidate === false || revalidate === void 0) return ONE_YEAR_SEC2;
|
|
1259
1458
|
if (typeof revalidate === "number" && revalidate > 0) {
|
|
1260
1459
|
return Math.min(Math.max(Math.ceil(revalidate), MIN_TTL_SEC), ONE_YEAR_SEC2);
|
|
@@ -1292,10 +1491,49 @@ async function readTagStates(state, tags) {
|
|
|
1292
1491
|
var IncrementalRedisCacheHandler = class {
|
|
1293
1492
|
state;
|
|
1294
1493
|
revalidatedTags;
|
|
1494
|
+
/**
|
|
1495
|
+
* Request-scoped memo of Redis entry reads. Next.js instantiates this class
|
|
1496
|
+
* per request (and calls resetRequestCache() at request boundaries when it
|
|
1497
|
+
* reuses an instance), so entries live at most one request. Bounded by the
|
|
1498
|
+
* number of distinct cache keys a single request touches.
|
|
1499
|
+
*/
|
|
1500
|
+
requestMemo = /* @__PURE__ */ new Map();
|
|
1295
1501
|
constructor(state, ctx) {
|
|
1296
1502
|
this.state = state;
|
|
1297
1503
|
this.revalidatedTags = normalizeTags(ctx?.revalidatedTags);
|
|
1298
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Redis entry read, deduplicated per request: parallel RSC renders or
|
|
1507
|
+
* repeated fetch() calls hitting the same cache key share one GET. The
|
|
1508
|
+
* memoized promise never rejects — errors are converted to a miss (with
|
|
1509
|
+
* the same timeout metric the inline path emitted before).
|
|
1510
|
+
*/
|
|
1511
|
+
readEntryDeduped(eKey) {
|
|
1512
|
+
const memoized = this.requestMemo.get(eKey);
|
|
1513
|
+
if (memoized) {
|
|
1514
|
+
this.state.emit({ type: "cache.get.deduped" });
|
|
1515
|
+
return memoized;
|
|
1516
|
+
}
|
|
1517
|
+
const read = withAbortSignal(
|
|
1518
|
+
"incremental.get",
|
|
1519
|
+
this.state.opts.abortTimeoutMs,
|
|
1520
|
+
async () => {
|
|
1521
|
+
const client = await this.state.conn.getOrConnect();
|
|
1522
|
+
if (!client) return null;
|
|
1523
|
+
return await decompressValue(await client.get(eKey));
|
|
1524
|
+
}
|
|
1525
|
+
).catch((err) => {
|
|
1526
|
+
if (err instanceof CacheTimeoutError) {
|
|
1527
|
+
this.state.emit({
|
|
1528
|
+
type: "redis.timeout",
|
|
1529
|
+
meta: { op: "incremental.get" }
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
return null;
|
|
1533
|
+
});
|
|
1534
|
+
this.requestMemo.set(eKey, read);
|
|
1535
|
+
return read;
|
|
1536
|
+
}
|
|
1299
1537
|
async get(cacheKey, ctx) {
|
|
1300
1538
|
try {
|
|
1301
1539
|
const requestedTags = normalizeTags([
|
|
@@ -1307,24 +1545,11 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1307
1545
|
const useRedis = !useLocal && gateRedis2(this.state);
|
|
1308
1546
|
let raw = null;
|
|
1309
1547
|
if (useRedis) {
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
async () => {
|
|
1315
|
-
const client = await this.state.conn.getOrConnect();
|
|
1316
|
-
if (!client) return null;
|
|
1317
|
-
return await client.get(eKey);
|
|
1318
|
-
}
|
|
1319
|
-
);
|
|
1320
|
-
} catch (err) {
|
|
1321
|
-
if (err instanceof CacheTimeoutError) {
|
|
1322
|
-
this.state.emit({ type: "redis.timeout", meta: { op: "incremental.get" } });
|
|
1323
|
-
}
|
|
1324
|
-
raw = null;
|
|
1325
|
-
}
|
|
1548
|
+
raw = await this.readEntryDeduped(eKey);
|
|
1549
|
+
}
|
|
1550
|
+
if (raw === null && (useLocal || this.state.opts.fallback !== "never")) {
|
|
1551
|
+
raw = this.state.memEntries.get(eKey);
|
|
1326
1552
|
}
|
|
1327
|
-
if (raw === null) raw = this.state.memEntries.get(eKey);
|
|
1328
1553
|
if (raw === null) {
|
|
1329
1554
|
this.state.emit({ type: "cache.miss" });
|
|
1330
1555
|
return null;
|
|
@@ -1337,7 +1562,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1337
1562
|
return null;
|
|
1338
1563
|
}
|
|
1339
1564
|
const tagStates = await readTagStates(this.state, tags);
|
|
1340
|
-
const isFetch = ctx?.kind === "FETCH";
|
|
1565
|
+
const isFetch = ctx?.kind === "FETCH" || ctx?.kindHint?.toLowerCase() === "fetch" || typeof parsed.value === "object" && parsed.value !== null && parsed.value.kind === "FETCH";
|
|
1341
1566
|
if (tagStates.some((s) => isTagStateExpired(s, parsed.lastModified, isFetch))) {
|
|
1342
1567
|
this.state.emit({ type: "cache.miss", meta: { reason: "tag-state-expired" } });
|
|
1343
1568
|
return null;
|
|
@@ -1360,6 +1585,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1360
1585
|
const record = { lastModified: Date.now(), value: data };
|
|
1361
1586
|
const serialized = serializeCacheRecord(record);
|
|
1362
1587
|
const eKey = entryKey2(this.state, cacheKey);
|
|
1588
|
+
this.requestMemo.delete(eKey);
|
|
1363
1589
|
const useRedis = !useLocal && gateRedis2(this.state);
|
|
1364
1590
|
if (useRedis) {
|
|
1365
1591
|
try {
|
|
@@ -1369,10 +1595,19 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1369
1595
|
async () => {
|
|
1370
1596
|
const client = await this.state.conn.getOrConnect();
|
|
1371
1597
|
if (!client) {
|
|
1372
|
-
this.state.
|
|
1598
|
+
if (this.state.opts.fallback !== "never") {
|
|
1599
|
+
this.state.memEntries.set(eKey, serialized, ttl);
|
|
1600
|
+
}
|
|
1373
1601
|
return;
|
|
1374
1602
|
}
|
|
1375
|
-
await client.set(
|
|
1603
|
+
await client.set(
|
|
1604
|
+
eKey,
|
|
1605
|
+
await compressValue(
|
|
1606
|
+
serialized,
|
|
1607
|
+
this.state.opts.rest.compression
|
|
1608
|
+
),
|
|
1609
|
+
{ EX: ttl }
|
|
1610
|
+
);
|
|
1376
1611
|
}
|
|
1377
1612
|
);
|
|
1378
1613
|
this.state.emit({ type: "cache.set", meta: { backend: "redis" } });
|
|
@@ -1383,8 +1618,15 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1383
1618
|
}
|
|
1384
1619
|
}
|
|
1385
1620
|
}
|
|
1386
|
-
this.state.
|
|
1387
|
-
|
|
1621
|
+
if (useLocal || this.state.opts.fallback !== "never") {
|
|
1622
|
+
this.state.memEntries.set(eKey, serialized, ttl);
|
|
1623
|
+
this.state.emit({ type: "cache.set", meta: { backend: "memory" } });
|
|
1624
|
+
} else {
|
|
1625
|
+
this.state.emit({
|
|
1626
|
+
type: "cache.set.failed",
|
|
1627
|
+
meta: { reason: "no-fallback" }
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1388
1630
|
} catch (err) {
|
|
1389
1631
|
this.state.logger.error("set() error", {
|
|
1390
1632
|
message: err.message
|
|
@@ -1438,6 +1680,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
1438
1680
|
}
|
|
1439
1681
|
}
|
|
1440
1682
|
resetRequestCache() {
|
|
1683
|
+
this.requestMemo.clear();
|
|
1441
1684
|
}
|
|
1442
1685
|
};
|
|
1443
1686
|
function createIncrementalCacheHandler(opts) {
|