@leejpsd/nextjs-cache-handler 0.1.1 → 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 +117 -2
- package/README.md +131 -30
- package/dist/cache-components/index.cjs +297 -52
- 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 +289 -52
- 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 +375 -76
- 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 +367 -76
- 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-CgriT7My.d.cts → types-Bl-rkLKX.d.cts} +59 -1
- package/dist/{types-CgriT7My.d.ts → types-Bl-rkLKX.d.ts} +59 -1
- package/package.json +23 -4
|
@@ -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,26 @@ 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/cache-components/index.ts
|
|
232
|
+
init_cjs_shims();
|
|
233
|
+
|
|
234
|
+
// src/cache-components/handler.ts
|
|
235
|
+
init_cjs_shims();
|
|
236
|
+
|
|
175
237
|
// src/shared/abort.ts
|
|
238
|
+
init_cjs_shims();
|
|
176
239
|
var CacheTimeoutError = class extends Error {
|
|
177
240
|
constructor(opName, ms) {
|
|
178
241
|
super(`[@leejpsd/nextjs-cache-handler] ${opName} timed out after ${ms}ms`);
|
|
@@ -204,7 +267,41 @@ async function withAbortSignal(opName, ms, fn) {
|
|
|
204
267
|
}
|
|
205
268
|
}
|
|
206
269
|
|
|
270
|
+
// src/shared/compress.ts
|
|
271
|
+
init_cjs_shims();
|
|
272
|
+
var gzipAsync = util.promisify(zlib.gzip);
|
|
273
|
+
var gunzipAsync = util.promisify(zlib.gunzip);
|
|
274
|
+
var brotliCompressAsync = util.promisify(zlib.brotliCompress);
|
|
275
|
+
var brotliDecompressAsync = util.promisify(zlib.brotliDecompress);
|
|
276
|
+
var GZIP_MARKER = "__ncgz__:";
|
|
277
|
+
var BROTLI_MARKER = "__ncbr__:";
|
|
278
|
+
var MIN_COMPRESS_BYTES = 1024;
|
|
279
|
+
var BROTLI_OPTS = {
|
|
280
|
+
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 5 }
|
|
281
|
+
};
|
|
282
|
+
async function compressValue(value, algo) {
|
|
283
|
+
if (!algo || Buffer.byteLength(value) < MIN_COMPRESS_BYTES) return value;
|
|
284
|
+
const input = Buffer.from(value);
|
|
285
|
+
if (algo === "gzip") {
|
|
286
|
+
return GZIP_MARKER + (await gzipAsync(input)).toString("base64");
|
|
287
|
+
}
|
|
288
|
+
return BROTLI_MARKER + (await brotliCompressAsync(input, BROTLI_OPTS)).toString("base64");
|
|
289
|
+
}
|
|
290
|
+
async function decompressValue(value) {
|
|
291
|
+
if (value === null) return null;
|
|
292
|
+
if (value.startsWith(GZIP_MARKER)) {
|
|
293
|
+
const buf = Buffer.from(value.slice(GZIP_MARKER.length), "base64");
|
|
294
|
+
return (await gunzipAsync(buf)).toString("utf8");
|
|
295
|
+
}
|
|
296
|
+
if (value.startsWith(BROTLI_MARKER)) {
|
|
297
|
+
const buf = Buffer.from(value.slice(BROTLI_MARKER.length), "base64");
|
|
298
|
+
return (await brotliDecompressAsync(buf)).toString("utf8");
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
|
|
207
303
|
// src/shared/client/index.ts
|
|
304
|
+
init_cjs_shims();
|
|
208
305
|
async function buildClient(source) {
|
|
209
306
|
if (typeof source === "function") {
|
|
210
307
|
return await source();
|
|
@@ -222,6 +319,10 @@ async function buildClient(source) {
|
|
|
222
319
|
const { adaptCluster: adaptCluster2, createIoredisCluster: createIoredisCluster2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
223
320
|
return adaptCluster2(createIoredisCluster2(source));
|
|
224
321
|
}
|
|
322
|
+
case "sentinel": {
|
|
323
|
+
const { adaptIoredis: adaptIoredis2, createIoredisSentinel: createIoredisSentinel2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
324
|
+
return adaptIoredis2(createIoredisSentinel2(source));
|
|
325
|
+
}
|
|
225
326
|
default: {
|
|
226
327
|
const exhaustive = source;
|
|
227
328
|
throw new Error(
|
|
@@ -231,28 +332,45 @@ async function buildClient(source) {
|
|
|
231
332
|
}
|
|
232
333
|
}
|
|
233
334
|
var ConnectionManager = class {
|
|
234
|
-
constructor(source, onError) {
|
|
335
|
+
constructor(source, onError, retry = {}) {
|
|
235
336
|
this.source = source;
|
|
236
337
|
this.onError = onError;
|
|
338
|
+
this.baseCooldownMs = retry.baseCooldownMs ?? 1e3;
|
|
339
|
+
this.maxCooldownMs = retry.maxCooldownMs ?? 3e4;
|
|
237
340
|
}
|
|
238
341
|
source;
|
|
239
342
|
onError;
|
|
240
343
|
client = null;
|
|
241
344
|
connecting = null;
|
|
242
|
-
|
|
345
|
+
consecutiveFailures = 0;
|
|
346
|
+
lastFailureAt = 0;
|
|
347
|
+
baseCooldownMs;
|
|
348
|
+
maxCooldownMs;
|
|
349
|
+
cooldownMs() {
|
|
350
|
+
if (this.consecutiveFailures === 0) return 0;
|
|
351
|
+
return Math.min(
|
|
352
|
+
this.maxCooldownMs,
|
|
353
|
+
this.baseCooldownMs * 2 ** (this.consecutiveFailures - 1)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
243
356
|
async getOrConnect() {
|
|
244
357
|
if (this.client?.isOpen) return this.client;
|
|
245
|
-
if (this.failed) return null;
|
|
246
358
|
if (this.connecting) return this.connecting;
|
|
359
|
+
if (Date.now() - this.lastFailureAt < this.cooldownMs()) return null;
|
|
360
|
+
const stale = this.client;
|
|
361
|
+
this.client = null;
|
|
247
362
|
this.connecting = (async () => {
|
|
363
|
+
disposeQuietly(stale);
|
|
248
364
|
try {
|
|
249
365
|
const c = await buildClient(this.source);
|
|
250
366
|
c.on("error", (err) => this.onError(err));
|
|
251
367
|
if (!c.isOpen) await c.connect();
|
|
252
368
|
this.client = c;
|
|
369
|
+
this.consecutiveFailures = 0;
|
|
253
370
|
return c;
|
|
254
371
|
} catch (err) {
|
|
255
|
-
this.
|
|
372
|
+
this.consecutiveFailures += 1;
|
|
373
|
+
this.lastFailureAt = Date.now();
|
|
256
374
|
this.onError(err);
|
|
257
375
|
return null;
|
|
258
376
|
} finally {
|
|
@@ -262,13 +380,24 @@ var ConnectionManager = class {
|
|
|
262
380
|
return this.connecting;
|
|
263
381
|
}
|
|
264
382
|
reset() {
|
|
383
|
+
disposeQuietly(this.client);
|
|
265
384
|
this.client = null;
|
|
266
385
|
this.connecting = null;
|
|
267
|
-
this.
|
|
386
|
+
this.consecutiveFailures = 0;
|
|
387
|
+
this.lastFailureAt = 0;
|
|
268
388
|
}
|
|
269
389
|
};
|
|
390
|
+
function disposeQuietly(client) {
|
|
391
|
+
if (!client) return;
|
|
392
|
+
const c = client;
|
|
393
|
+
try {
|
|
394
|
+
(c.dispose ?? c.destroy ?? c.disconnect)?.call(c);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
270
398
|
|
|
271
399
|
// src/shared/logger.ts
|
|
400
|
+
init_cjs_shims();
|
|
272
401
|
var defaultLogger = {
|
|
273
402
|
debug: () => {
|
|
274
403
|
},
|
|
@@ -284,6 +413,9 @@ var defaultLogger = {
|
|
|
284
413
|
}
|
|
285
414
|
};
|
|
286
415
|
|
|
416
|
+
// src/shared/lua/index.ts
|
|
417
|
+
init_cjs_shims();
|
|
418
|
+
|
|
287
419
|
// src/shared/lua/set-with-tags.lua
|
|
288
420
|
var set_with_tags_default = `-- set-with-tags.lua
|
|
289
421
|
--
|
|
@@ -387,13 +519,19 @@ async function execLuaScript(client, name, keys, args) {
|
|
|
387
519
|
}
|
|
388
520
|
|
|
389
521
|
// src/shared/memory-fallback.ts
|
|
522
|
+
init_cjs_shims();
|
|
390
523
|
var MAX_TIMER_MS = 2147483647;
|
|
391
524
|
function clampTimerMs(ms) {
|
|
392
525
|
return Math.min(Math.max(0, ms), MAX_TIMER_MS);
|
|
393
526
|
}
|
|
527
|
+
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
394
528
|
var MemoryStore = class {
|
|
395
529
|
kv = /* @__PURE__ */ new Map();
|
|
396
530
|
timers = /* @__PURE__ */ new Map();
|
|
531
|
+
maxEntries;
|
|
532
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
533
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
534
|
+
}
|
|
397
535
|
set(key, value, ttlSeconds) {
|
|
398
536
|
this.delete(key);
|
|
399
537
|
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
@@ -403,6 +541,10 @@ var MemoryStore = class {
|
|
|
403
541
|
timer.unref();
|
|
404
542
|
}
|
|
405
543
|
this.timers.set(key, timer);
|
|
544
|
+
while (this.kv.size > this.maxEntries) {
|
|
545
|
+
const oldest = this.kv.keys().next().value;
|
|
546
|
+
this.delete(oldest);
|
|
547
|
+
}
|
|
406
548
|
}
|
|
407
549
|
get(key) {
|
|
408
550
|
const entry = this.kv.get(key);
|
|
@@ -411,6 +553,8 @@ var MemoryStore = class {
|
|
|
411
553
|
this.delete(key);
|
|
412
554
|
return null;
|
|
413
555
|
}
|
|
556
|
+
this.kv.delete(key);
|
|
557
|
+
this.kv.set(key, entry);
|
|
414
558
|
return entry.value;
|
|
415
559
|
}
|
|
416
560
|
delete(key) {
|
|
@@ -445,13 +589,19 @@ var MemorySetStore = class {
|
|
|
445
589
|
sets = /* @__PURE__ */ new Map();
|
|
446
590
|
expiresAt = /* @__PURE__ */ new Map();
|
|
447
591
|
timers = /* @__PURE__ */ new Map();
|
|
592
|
+
maxSets;
|
|
593
|
+
constructor(maxSets = DEFAULT_MAX_ENTRIES) {
|
|
594
|
+
this.maxSets = Math.max(1, maxSets);
|
|
595
|
+
}
|
|
448
596
|
sAdd(key, member) {
|
|
449
597
|
const members = Array.isArray(member) ? member : [member];
|
|
450
598
|
let set = this.sets.get(key);
|
|
451
599
|
if (!set) {
|
|
452
600
|
set = /* @__PURE__ */ new Set();
|
|
453
|
-
|
|
601
|
+
} else {
|
|
602
|
+
this.sets.delete(key);
|
|
454
603
|
}
|
|
604
|
+
this.sets.set(key, set);
|
|
455
605
|
let added = 0;
|
|
456
606
|
for (const m of members) {
|
|
457
607
|
if (!set.has(m)) {
|
|
@@ -459,6 +609,10 @@ var MemorySetStore = class {
|
|
|
459
609
|
added += 1;
|
|
460
610
|
}
|
|
461
611
|
}
|
|
612
|
+
while (this.sets.size > this.maxSets) {
|
|
613
|
+
const oldest = this.sets.keys().next().value;
|
|
614
|
+
this.delete(oldest);
|
|
615
|
+
}
|
|
462
616
|
return added;
|
|
463
617
|
}
|
|
464
618
|
sMembers(key) {
|
|
@@ -511,6 +665,7 @@ var MemorySetStore = class {
|
|
|
511
665
|
};
|
|
512
666
|
|
|
513
667
|
// src/shared/metrics.ts
|
|
668
|
+
init_cjs_shims();
|
|
514
669
|
var counters = /* @__PURE__ */ new Map();
|
|
515
670
|
function bump(event) {
|
|
516
671
|
const existing = counters.get(event.type);
|
|
@@ -539,6 +694,7 @@ function createEmitter(onMetric) {
|
|
|
539
694
|
}
|
|
540
695
|
|
|
541
696
|
// src/shared/namespace.ts
|
|
697
|
+
init_cjs_shims();
|
|
542
698
|
var FALLBACK = "unversioned";
|
|
543
699
|
function resolveBuildNamespace(override) {
|
|
544
700
|
if (override !== void 0) {
|
|
@@ -552,6 +708,7 @@ function buildKey(prefix, namespace, rest, hashTag = false) {
|
|
|
552
708
|
}
|
|
553
709
|
|
|
554
710
|
// src/shared/build-phase.ts
|
|
711
|
+
init_cjs_shims();
|
|
555
712
|
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
556
713
|
function isBuildPhase() {
|
|
557
714
|
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
|
|
@@ -564,6 +721,7 @@ function shouldUseRedis(opts) {
|
|
|
564
721
|
}
|
|
565
722
|
|
|
566
723
|
// src/cache-components/serialize.ts
|
|
724
|
+
init_cjs_shims();
|
|
567
725
|
async function readStreamFully(stream) {
|
|
568
726
|
const reader = stream.getReader();
|
|
569
727
|
const chunks = [];
|
|
@@ -606,6 +764,7 @@ function decodeEnvelope(raw) {
|
|
|
606
764
|
}
|
|
607
765
|
|
|
608
766
|
// src/cache-components/swr.ts
|
|
767
|
+
init_cjs_shims();
|
|
609
768
|
var SECOND_MS = 1e3;
|
|
610
769
|
function partitionEntry(entry, now = Date.now()) {
|
|
611
770
|
const ageMs = now - entry.timestamp;
|
|
@@ -632,7 +791,9 @@ var DEFAULT_KEY_PREFIX = "next-cache:";
|
|
|
632
791
|
var ENTRY_SUFFIX = "entry:";
|
|
633
792
|
var TAG_SUFFIX = "tag:";
|
|
634
793
|
var TAG_EXP_SUFFIX = "tag-expiration:";
|
|
794
|
+
var LOCK_SUFFIX = "lock:";
|
|
635
795
|
var DEFAULT_ABORT_MS = 1500;
|
|
796
|
+
var DEFAULT_LOCK_TTL_SEC = 10;
|
|
636
797
|
var ONE_YEAR_SEC = 60 * 60 * 24 * 365;
|
|
637
798
|
var FALLBACK_TTL_SEC = 3600;
|
|
638
799
|
var TAG_EXP_MARKER_TTL_SEC = 60 * 60 * 24 * 7;
|
|
@@ -655,16 +816,19 @@ function init(opts) {
|
|
|
655
816
|
staleWhileRevalidate: opts.staleWhileRevalidate ?? true,
|
|
656
817
|
hashTag: opts.hashTag ?? false,
|
|
657
818
|
keyPrefix: opts.keyPrefix ?? DEFAULT_KEY_PREFIX,
|
|
819
|
+
singleFlight: opts.singleFlight ?? false,
|
|
820
|
+
singleFlightLockTtlSec: opts.singleFlightLockTtlSec ?? DEFAULT_LOCK_TTL_SEC,
|
|
658
821
|
rest: opts
|
|
659
822
|
},
|
|
660
823
|
logger,
|
|
661
824
|
emit,
|
|
662
825
|
conn,
|
|
663
|
-
memEntries: new MemoryStore(),
|
|
664
|
-
memTags: new MemorySetStore(),
|
|
826
|
+
memEntries: new MemoryStore(opts.memoryMaxEntries),
|
|
827
|
+
memTags: new MemorySetStore(opts.memoryMaxEntries),
|
|
665
828
|
memTagExp: /* @__PURE__ */ new Map(),
|
|
666
829
|
localTagTimestamps: /* @__PURE__ */ new Map(),
|
|
667
|
-
resolveNs: () => resolveBuildNamespace(opts.buildNamespace)
|
|
830
|
+
resolveNs: () => resolveBuildNamespace(opts.buildNamespace),
|
|
831
|
+
instanceId: process.env.HOSTNAME || process.env.ECS_TASK_ID || `pid-${process.pid}`
|
|
668
832
|
};
|
|
669
833
|
}
|
|
670
834
|
function entryKey(state, cacheKey) {
|
|
@@ -691,6 +855,40 @@ function tagExpKey(state, tag) {
|
|
|
691
855
|
state.opts.hashTag
|
|
692
856
|
);
|
|
693
857
|
}
|
|
858
|
+
function lockKey(state, cacheKey) {
|
|
859
|
+
return buildKey(
|
|
860
|
+
`${state.opts.keyPrefix}${LOCK_SUFFIX}`,
|
|
861
|
+
state.resolveNs(),
|
|
862
|
+
cacheKey,
|
|
863
|
+
state.opts.hashTag
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
async function tryAcquireRefreshLock(state, cacheKey) {
|
|
867
|
+
if (!state.opts.singleFlight) return false;
|
|
868
|
+
if (!gateRedis(state)) return false;
|
|
869
|
+
try {
|
|
870
|
+
return await withAbortSignal(
|
|
871
|
+
"cacheHandlers.singleFlight.lock",
|
|
872
|
+
state.opts.abortTimeoutMs,
|
|
873
|
+
async () => {
|
|
874
|
+
const client = await state.conn.getOrConnect();
|
|
875
|
+
if (!client) return false;
|
|
876
|
+
const result = await execLuaScript(
|
|
877
|
+
client,
|
|
878
|
+
"refreshTagLock",
|
|
879
|
+
[lockKey(state, cacheKey)],
|
|
880
|
+
[
|
|
881
|
+
state.instanceId,
|
|
882
|
+
String(state.opts.singleFlightLockTtlSec)
|
|
883
|
+
]
|
|
884
|
+
);
|
|
885
|
+
return result === 1;
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
} catch {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
694
892
|
function resolveTtlSeconds(entry) {
|
|
695
893
|
if (Number.isFinite(entry.expire) && entry.expire > 0) {
|
|
696
894
|
return Math.max(60, Math.ceil(entry.expire));
|
|
@@ -743,6 +941,12 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
743
941
|
state.emit({ type: "cache.miss", ms: Date.now() - start });
|
|
744
942
|
return void 0;
|
|
745
943
|
}
|
|
944
|
+
try {
|
|
945
|
+
raw = await decompressValue(raw);
|
|
946
|
+
} catch {
|
|
947
|
+
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
948
|
+
return void 0;
|
|
949
|
+
}
|
|
746
950
|
envelope = decodeEnvelope(raw);
|
|
747
951
|
if (!envelope) {
|
|
748
952
|
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
@@ -785,11 +989,28 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
785
989
|
return void 0;
|
|
786
990
|
}
|
|
787
991
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
992
|
+
if (partition.freshness === "stale") {
|
|
993
|
+
if (state.opts.singleFlight) {
|
|
994
|
+
const isLeader = await tryAcquireRefreshLock(state, cacheKey);
|
|
995
|
+
state.emit({
|
|
996
|
+
type: isLeader ? "cache.stale.refresh.leader" : "cache.stale.refresh.follower",
|
|
997
|
+
ms: Date.now() - start,
|
|
998
|
+
meta: { freshness: partition.freshness }
|
|
999
|
+
});
|
|
1000
|
+
} else {
|
|
1001
|
+
state.emit({
|
|
1002
|
+
type: "cache.stale",
|
|
1003
|
+
ms: Date.now() - start,
|
|
1004
|
+
meta: { freshness: partition.freshness }
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
} else {
|
|
1008
|
+
state.emit({
|
|
1009
|
+
type: "cache.hit",
|
|
1010
|
+
ms: Date.now() - start,
|
|
1011
|
+
meta: { freshness: partition.freshness }
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
793
1014
|
return {
|
|
794
1015
|
value: bufferToStream(Buffer.from(envelope.value, "base64")),
|
|
795
1016
|
tags: envelope.tags,
|
|
@@ -854,10 +1075,14 @@ async function setImpl(state, cacheKey, pendingEntry) {
|
|
|
854
1075
|
}
|
|
855
1076
|
return;
|
|
856
1077
|
}
|
|
1078
|
+
const wirePayload = await compressValue(
|
|
1079
|
+
payload,
|
|
1080
|
+
state.opts.rest.compression
|
|
1081
|
+
);
|
|
857
1082
|
if (entry.tags.length === 0) {
|
|
858
|
-
await client.set(eKey,
|
|
1083
|
+
await client.set(eKey, wirePayload, { EX: ttl });
|
|
859
1084
|
} else {
|
|
860
|
-
await luaSetWithTags(client, state, eKey,
|
|
1085
|
+
await luaSetWithTags(client, state, eKey, wirePayload, ttl, entry.tags);
|
|
861
1086
|
}
|
|
862
1087
|
}
|
|
863
1088
|
);
|
|
@@ -893,28 +1118,36 @@ async function refreshTagsImpl(state) {
|
|
|
893
1118
|
async () => {
|
|
894
1119
|
const client = await state.conn.getOrConnect();
|
|
895
1120
|
if (!client) return;
|
|
896
|
-
const
|
|
897
|
-
const
|
|
1121
|
+
const ns = state.opts.hashTag ? `{${state.resolveNs()}}` : state.resolveNs();
|
|
1122
|
+
const pattern = `${escapeMatch(
|
|
1123
|
+
`${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`
|
|
1124
|
+
)}*`;
|
|
1125
|
+
const scanStartMs = Date.now();
|
|
1126
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
898
1127
|
for await (const chunk of client.scanIterator({
|
|
899
1128
|
MATCH: pattern,
|
|
900
1129
|
COUNT: 100
|
|
901
1130
|
})) {
|
|
902
1131
|
const keys = Array.isArray(chunk) ? chunk.map(String) : [String(chunk)];
|
|
903
|
-
|
|
904
|
-
|
|
1132
|
+
if (keys.length === 0) continue;
|
|
1133
|
+
const values = await client.mGet(keys);
|
|
1134
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
1135
|
+
const v = values[i];
|
|
1136
|
+
if (!v) continue;
|
|
1137
|
+
const ts = Number.parseInt(v, 10);
|
|
1138
|
+
if (!Number.isFinite(ts)) continue;
|
|
1139
|
+
const key = keys[i];
|
|
1140
|
+
if (key === void 0) continue;
|
|
1141
|
+
const tag = stripExpPrefix(state, key);
|
|
1142
|
+
if (tag) fresh.set(tag, ts);
|
|
1143
|
+
}
|
|
905
1144
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (!v) continue;
|
|
911
|
-
const ts = Number.parseInt(v, 10);
|
|
912
|
-
if (!Number.isFinite(ts)) continue;
|
|
913
|
-
const key = seen[i];
|
|
914
|
-
if (key === void 0) continue;
|
|
915
|
-
const tag = stripExpPrefix(state, key);
|
|
916
|
-
if (tag) state.localTagTimestamps.set(tag, ts);
|
|
1145
|
+
for (const [tag, ts] of state.localTagTimestamps) {
|
|
1146
|
+
if (ts >= scanStartMs && (fresh.get(tag) ?? 0) < ts) {
|
|
1147
|
+
fresh.set(tag, ts);
|
|
1148
|
+
}
|
|
917
1149
|
}
|
|
1150
|
+
state.localTagTimestamps = fresh;
|
|
918
1151
|
}
|
|
919
1152
|
);
|
|
920
1153
|
} catch (err) {
|
|
@@ -923,6 +1156,19 @@ async function refreshTagsImpl(state) {
|
|
|
923
1156
|
});
|
|
924
1157
|
}
|
|
925
1158
|
}
|
|
1159
|
+
function escapeMatch(s) {
|
|
1160
|
+
return s.replace(/[\\*?[\]]/g, "\\$&");
|
|
1161
|
+
}
|
|
1162
|
+
var MAX_LOCAL_TAG_ENTRIES = 1e4;
|
|
1163
|
+
function pruneOldestByTimestamp(map, maxEntries) {
|
|
1164
|
+
if (map.size <= maxEntries) return;
|
|
1165
|
+
const byAge = [...map.entries()].sort((a, b) => a[1] - b[1]);
|
|
1166
|
+
const drop = map.size - maxEntries;
|
|
1167
|
+
for (let i = 0; i < drop; i += 1) {
|
|
1168
|
+
const entry = byAge[i];
|
|
1169
|
+
if (entry) map.delete(entry[0]);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
926
1172
|
function stripExpPrefix(state, fullKey) {
|
|
927
1173
|
const ns = state.resolveNs();
|
|
928
1174
|
const prefix = state.opts.hashTag ? `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}{${ns}}:` : `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`;
|
|
@@ -974,6 +1220,7 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
974
1220
|
state.localTagTimestamps.set(t, now);
|
|
975
1221
|
state.memTagExp.set(t, now);
|
|
976
1222
|
}
|
|
1223
|
+
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
977
1224
|
if (!gateRedis(state)) {
|
|
978
1225
|
if (isHardExpire) {
|
|
979
1226
|
for (const t of tags) {
|
|
@@ -1003,20 +1250,18 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1003
1250
|
}
|
|
1004
1251
|
return;
|
|
1005
1252
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1253
|
+
await Promise.all(
|
|
1254
|
+
tags.map(
|
|
1255
|
+
(t) => isHardExpire ? execLuaScript(
|
|
1009
1256
|
client,
|
|
1010
1257
|
"revalidateHard",
|
|
1011
1258
|
[tagKey(state, t), tagExpKey(state, t)],
|
|
1012
1259
|
[String(now), String(TAG_EXP_MARKER_TTL_SEC)]
|
|
1013
|
-
)
|
|
1014
|
-
} else {
|
|
1015
|
-
await client.set(tagExpKey(state, t), String(now), {
|
|
1260
|
+
) : client.set(tagExpKey(state, t), String(now), {
|
|
1016
1261
|
EX: TAG_EXP_MARKER_TTL_SEC
|
|
1017
|
-
})
|
|
1018
|
-
|
|
1019
|
-
|
|
1262
|
+
})
|
|
1263
|
+
)
|
|
1264
|
+
);
|
|
1020
1265
|
}
|
|
1021
1266
|
);
|
|
1022
1267
|
state.emit({
|