@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
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import path2 from 'path';
|
|
2
|
+
import 'url';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import { gzip, gunzip, brotliCompress, brotliDecompress, constants } from 'zlib';
|
|
6
|
+
|
|
1
7
|
var __defProp = Object.defineProperty;
|
|
2
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
9
|
var __esm = (fn, res) => function __init() {
|
|
10
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
11
|
};
|
|
@@ -13,6 +13,27 @@ var __export = (target, all) => {
|
|
|
13
13
|
for (var name in all)
|
|
14
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
|
+
var init_esm_shims = __esm({
|
|
17
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
function nodeRequire(id) {
|
|
21
|
+
try {
|
|
22
|
+
return packageRequire(id);
|
|
23
|
+
} catch {
|
|
24
|
+
return cwdRequire(id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var packageRequire, cwdRequire;
|
|
28
|
+
var init_node_require = __esm({
|
|
29
|
+
"src/shared/node-require.ts"() {
|
|
30
|
+
init_esm_shims();
|
|
31
|
+
packageRequire = createRequire(import.meta.url);
|
|
32
|
+
cwdRequire = createRequire(
|
|
33
|
+
path2.join(process.cwd(), "__resolve-anchor__.js")
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
16
37
|
|
|
17
38
|
// src/shared/client/adapter-redis.ts
|
|
18
39
|
var adapter_redis_exports = {};
|
|
@@ -24,7 +45,7 @@ function adaptRedisV5(client) {
|
|
|
24
45
|
return client;
|
|
25
46
|
}
|
|
26
47
|
function createRedisV5Client(config) {
|
|
27
|
-
const { createClient } =
|
|
48
|
+
const { createClient } = nodeRequire("redis");
|
|
28
49
|
const useTls = config.tls ?? config.url.startsWith("rediss://");
|
|
29
50
|
const opts = {
|
|
30
51
|
url: config.url,
|
|
@@ -39,6 +60,8 @@ function createRedisV5Client(config) {
|
|
|
39
60
|
}
|
|
40
61
|
var init_adapter_redis = __esm({
|
|
41
62
|
"src/shared/client/adapter-redis.ts"() {
|
|
63
|
+
init_esm_shims();
|
|
64
|
+
init_node_require();
|
|
42
65
|
}
|
|
43
66
|
});
|
|
44
67
|
|
|
@@ -48,7 +71,8 @@ __export(adapter_ioredis_exports, {
|
|
|
48
71
|
adaptCluster: () => adaptCluster,
|
|
49
72
|
adaptIoredis: () => adaptIoredis,
|
|
50
73
|
createIoredisClient: () => createIoredisClient,
|
|
51
|
-
createIoredisCluster: () => createIoredisCluster
|
|
74
|
+
createIoredisCluster: () => createIoredisCluster,
|
|
75
|
+
createIoredisSentinel: () => createIoredisSentinel
|
|
52
76
|
});
|
|
53
77
|
function adapt(client, isClusterClient = false) {
|
|
54
78
|
return {
|
|
@@ -131,6 +155,9 @@ function adapt(client, isClusterClient = false) {
|
|
|
131
155
|
on: (event, listener) => {
|
|
132
156
|
client.on(event, listener);
|
|
133
157
|
return client;
|
|
158
|
+
},
|
|
159
|
+
dispose: () => {
|
|
160
|
+
client.disconnect();
|
|
134
161
|
}
|
|
135
162
|
};
|
|
136
163
|
}
|
|
@@ -141,18 +168,36 @@ function adaptCluster(cluster) {
|
|
|
141
168
|
return adapt(cluster, true);
|
|
142
169
|
}
|
|
143
170
|
function createIoredisClient(config) {
|
|
144
|
-
const IORedis =
|
|
171
|
+
const IORedis = nodeRequire("ioredis");
|
|
145
172
|
const Ctor = IORedis.default ?? IORedis;
|
|
146
173
|
return new Ctor(config.url, {
|
|
147
174
|
password: config.password,
|
|
148
175
|
connectTimeout: config.connectTimeout ?? 1500,
|
|
149
176
|
tls: config.tls ? {} : void 0,
|
|
150
177
|
lazyConnect: true,
|
|
151
|
-
maxRetriesPerRequest: 1
|
|
178
|
+
maxRetriesPerRequest: 1,
|
|
179
|
+
// Batch same-tick commands into one RTT (matches redis@5's default
|
|
180
|
+
// behavior) — makes Promise.all fan-outs a single pipeline.
|
|
181
|
+
enableAutoPipelining: true
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function createIoredisSentinel(config) {
|
|
185
|
+
const IORedis = nodeRequire("ioredis");
|
|
186
|
+
const Ctor = IORedis.default ?? IORedis;
|
|
187
|
+
return new Ctor({
|
|
188
|
+
sentinels: config.sentinels,
|
|
189
|
+
name: config.name,
|
|
190
|
+
password: config.password,
|
|
191
|
+
sentinelPassword: config.sentinelPassword,
|
|
192
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
193
|
+
tls: config.tls ? {} : void 0,
|
|
194
|
+
lazyConnect: true,
|
|
195
|
+
maxRetriesPerRequest: 1,
|
|
196
|
+
enableAutoPipelining: true
|
|
152
197
|
});
|
|
153
198
|
}
|
|
154
199
|
function createIoredisCluster(config) {
|
|
155
|
-
const IORedis =
|
|
200
|
+
const IORedis = nodeRequire("ioredis");
|
|
156
201
|
const ClusterCtor = IORedis.Cluster ?? IORedis.default.Cluster;
|
|
157
202
|
return new ClusterCtor(config.nodes, {
|
|
158
203
|
redisOptions: {
|
|
@@ -160,15 +205,26 @@ function createIoredisCluster(config) {
|
|
|
160
205
|
tls: config.tls ? {} : void 0
|
|
161
206
|
},
|
|
162
207
|
enableReadyCheck: true,
|
|
163
|
-
lazyConnect: true
|
|
208
|
+
lazyConnect: true,
|
|
209
|
+
// Same-slot commands issued in the same tick share one pipeline.
|
|
210
|
+
enableAutoPipelining: true
|
|
164
211
|
});
|
|
165
212
|
}
|
|
166
213
|
var init_adapter_ioredis = __esm({
|
|
167
214
|
"src/shared/client/adapter-ioredis.ts"() {
|
|
215
|
+
init_esm_shims();
|
|
216
|
+
init_node_require();
|
|
168
217
|
}
|
|
169
218
|
});
|
|
170
219
|
|
|
220
|
+
// src/incremental/index.ts
|
|
221
|
+
init_esm_shims();
|
|
222
|
+
|
|
223
|
+
// src/incremental/handler.ts
|
|
224
|
+
init_esm_shims();
|
|
225
|
+
|
|
171
226
|
// src/shared/abort.ts
|
|
227
|
+
init_esm_shims();
|
|
172
228
|
var CacheTimeoutError = class extends Error {
|
|
173
229
|
constructor(opName, ms) {
|
|
174
230
|
super(`[@leejpsd/nextjs-cache-handler] ${opName} timed out after ${ms}ms`);
|
|
@@ -200,7 +256,41 @@ async function withAbortSignal(opName, ms, fn) {
|
|
|
200
256
|
}
|
|
201
257
|
}
|
|
202
258
|
|
|
259
|
+
// src/shared/compress.ts
|
|
260
|
+
init_esm_shims();
|
|
261
|
+
var gzipAsync = promisify(gzip);
|
|
262
|
+
var gunzipAsync = promisify(gunzip);
|
|
263
|
+
var brotliCompressAsync = promisify(brotliCompress);
|
|
264
|
+
var brotliDecompressAsync = promisify(brotliDecompress);
|
|
265
|
+
var GZIP_MARKER = "__ncgz__:";
|
|
266
|
+
var BROTLI_MARKER = "__ncbr__:";
|
|
267
|
+
var MIN_COMPRESS_BYTES = 1024;
|
|
268
|
+
var BROTLI_OPTS = {
|
|
269
|
+
params: { [constants.BROTLI_PARAM_QUALITY]: 5 }
|
|
270
|
+
};
|
|
271
|
+
async function compressValue(value, algo) {
|
|
272
|
+
if (!algo || Buffer.byteLength(value) < MIN_COMPRESS_BYTES) return value;
|
|
273
|
+
const input = Buffer.from(value);
|
|
274
|
+
if (algo === "gzip") {
|
|
275
|
+
return GZIP_MARKER + (await gzipAsync(input)).toString("base64");
|
|
276
|
+
}
|
|
277
|
+
return BROTLI_MARKER + (await brotliCompressAsync(input, BROTLI_OPTS)).toString("base64");
|
|
278
|
+
}
|
|
279
|
+
async function decompressValue(value) {
|
|
280
|
+
if (value === null) return null;
|
|
281
|
+
if (value.startsWith(GZIP_MARKER)) {
|
|
282
|
+
const buf = Buffer.from(value.slice(GZIP_MARKER.length), "base64");
|
|
283
|
+
return (await gunzipAsync(buf)).toString("utf8");
|
|
284
|
+
}
|
|
285
|
+
if (value.startsWith(BROTLI_MARKER)) {
|
|
286
|
+
const buf = Buffer.from(value.slice(BROTLI_MARKER.length), "base64");
|
|
287
|
+
return (await brotliDecompressAsync(buf)).toString("utf8");
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
|
|
203
292
|
// src/shared/client/index.ts
|
|
293
|
+
init_esm_shims();
|
|
204
294
|
async function buildClient(source) {
|
|
205
295
|
if (typeof source === "function") {
|
|
206
296
|
return await source();
|
|
@@ -218,6 +308,10 @@ async function buildClient(source) {
|
|
|
218
308
|
const { adaptCluster: adaptCluster2, createIoredisCluster: createIoredisCluster2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
219
309
|
return adaptCluster2(createIoredisCluster2(source));
|
|
220
310
|
}
|
|
311
|
+
case "sentinel": {
|
|
312
|
+
const { adaptIoredis: adaptIoredis2, createIoredisSentinel: createIoredisSentinel2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
313
|
+
return adaptIoredis2(createIoredisSentinel2(source));
|
|
314
|
+
}
|
|
221
315
|
default: {
|
|
222
316
|
const exhaustive = source;
|
|
223
317
|
throw new Error(
|
|
@@ -227,28 +321,45 @@ async function buildClient(source) {
|
|
|
227
321
|
}
|
|
228
322
|
}
|
|
229
323
|
var ConnectionManager = class {
|
|
230
|
-
constructor(source, onError) {
|
|
324
|
+
constructor(source, onError, retry = {}) {
|
|
231
325
|
this.source = source;
|
|
232
326
|
this.onError = onError;
|
|
327
|
+
this.baseCooldownMs = retry.baseCooldownMs ?? 1e3;
|
|
328
|
+
this.maxCooldownMs = retry.maxCooldownMs ?? 3e4;
|
|
233
329
|
}
|
|
234
330
|
source;
|
|
235
331
|
onError;
|
|
236
332
|
client = null;
|
|
237
333
|
connecting = null;
|
|
238
|
-
|
|
334
|
+
consecutiveFailures = 0;
|
|
335
|
+
lastFailureAt = 0;
|
|
336
|
+
baseCooldownMs;
|
|
337
|
+
maxCooldownMs;
|
|
338
|
+
cooldownMs() {
|
|
339
|
+
if (this.consecutiveFailures === 0) return 0;
|
|
340
|
+
return Math.min(
|
|
341
|
+
this.maxCooldownMs,
|
|
342
|
+
this.baseCooldownMs * 2 ** (this.consecutiveFailures - 1)
|
|
343
|
+
);
|
|
344
|
+
}
|
|
239
345
|
async getOrConnect() {
|
|
240
346
|
if (this.client?.isOpen) return this.client;
|
|
241
|
-
if (this.failed) return null;
|
|
242
347
|
if (this.connecting) return this.connecting;
|
|
348
|
+
if (Date.now() - this.lastFailureAt < this.cooldownMs()) return null;
|
|
349
|
+
const stale = this.client;
|
|
350
|
+
this.client = null;
|
|
243
351
|
this.connecting = (async () => {
|
|
352
|
+
disposeQuietly(stale);
|
|
244
353
|
try {
|
|
245
354
|
const c = await buildClient(this.source);
|
|
246
355
|
c.on("error", (err) => this.onError(err));
|
|
247
356
|
if (!c.isOpen) await c.connect();
|
|
248
357
|
this.client = c;
|
|
358
|
+
this.consecutiveFailures = 0;
|
|
249
359
|
return c;
|
|
250
360
|
} catch (err) {
|
|
251
|
-
this.
|
|
361
|
+
this.consecutiveFailures += 1;
|
|
362
|
+
this.lastFailureAt = Date.now();
|
|
252
363
|
this.onError(err);
|
|
253
364
|
return null;
|
|
254
365
|
} finally {
|
|
@@ -258,13 +369,24 @@ var ConnectionManager = class {
|
|
|
258
369
|
return this.connecting;
|
|
259
370
|
}
|
|
260
371
|
reset() {
|
|
372
|
+
disposeQuietly(this.client);
|
|
261
373
|
this.client = null;
|
|
262
374
|
this.connecting = null;
|
|
263
|
-
this.
|
|
375
|
+
this.consecutiveFailures = 0;
|
|
376
|
+
this.lastFailureAt = 0;
|
|
264
377
|
}
|
|
265
378
|
};
|
|
379
|
+
function disposeQuietly(client) {
|
|
380
|
+
if (!client) return;
|
|
381
|
+
const c = client;
|
|
382
|
+
try {
|
|
383
|
+
(c.dispose ?? c.destroy ?? c.disconnect)?.call(c);
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
266
387
|
|
|
267
388
|
// src/shared/logger.ts
|
|
389
|
+
init_esm_shims();
|
|
268
390
|
var defaultLogger = {
|
|
269
391
|
debug: () => {
|
|
270
392
|
},
|
|
@@ -281,13 +403,19 @@ var defaultLogger = {
|
|
|
281
403
|
};
|
|
282
404
|
|
|
283
405
|
// src/shared/memory-fallback.ts
|
|
406
|
+
init_esm_shims();
|
|
284
407
|
var MAX_TIMER_MS = 2147483647;
|
|
285
408
|
function clampTimerMs(ms) {
|
|
286
409
|
return Math.min(Math.max(0, ms), MAX_TIMER_MS);
|
|
287
410
|
}
|
|
411
|
+
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
288
412
|
var MemoryStore = class {
|
|
289
413
|
kv = /* @__PURE__ */ new Map();
|
|
290
414
|
timers = /* @__PURE__ */ new Map();
|
|
415
|
+
maxEntries;
|
|
416
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
417
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
418
|
+
}
|
|
291
419
|
set(key, value, ttlSeconds) {
|
|
292
420
|
this.delete(key);
|
|
293
421
|
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
@@ -297,6 +425,10 @@ var MemoryStore = class {
|
|
|
297
425
|
timer.unref();
|
|
298
426
|
}
|
|
299
427
|
this.timers.set(key, timer);
|
|
428
|
+
while (this.kv.size > this.maxEntries) {
|
|
429
|
+
const oldest = this.kv.keys().next().value;
|
|
430
|
+
this.delete(oldest);
|
|
431
|
+
}
|
|
300
432
|
}
|
|
301
433
|
get(key) {
|
|
302
434
|
const entry = this.kv.get(key);
|
|
@@ -305,6 +437,8 @@ var MemoryStore = class {
|
|
|
305
437
|
this.delete(key);
|
|
306
438
|
return null;
|
|
307
439
|
}
|
|
440
|
+
this.kv.delete(key);
|
|
441
|
+
this.kv.set(key, entry);
|
|
308
442
|
return entry.value;
|
|
309
443
|
}
|
|
310
444
|
delete(key) {
|
|
@@ -337,6 +471,7 @@ var MemoryStore = class {
|
|
|
337
471
|
};
|
|
338
472
|
|
|
339
473
|
// src/shared/metrics.ts
|
|
474
|
+
init_esm_shims();
|
|
340
475
|
var counters = /* @__PURE__ */ new Map();
|
|
341
476
|
function bump(event) {
|
|
342
477
|
const existing = counters.get(event.type);
|
|
@@ -365,6 +500,7 @@ function createEmitter(onMetric) {
|
|
|
365
500
|
}
|
|
366
501
|
|
|
367
502
|
// src/shared/namespace.ts
|
|
503
|
+
init_esm_shims();
|
|
368
504
|
var FALLBACK = "unversioned";
|
|
369
505
|
function resolveBuildNamespace(override) {
|
|
370
506
|
if (override !== void 0) {
|
|
@@ -378,6 +514,7 @@ function buildKey(prefix, namespace, rest, hashTag = false) {
|
|
|
378
514
|
}
|
|
379
515
|
|
|
380
516
|
// src/shared/build-phase.ts
|
|
517
|
+
init_esm_shims();
|
|
381
518
|
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
382
519
|
function isBuildPhase() {
|
|
383
520
|
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
|
|
@@ -390,6 +527,7 @@ function shouldUseRedis(opts) {
|
|
|
390
527
|
}
|
|
391
528
|
|
|
392
529
|
// src/incremental/serialize.ts
|
|
530
|
+
init_esm_shims();
|
|
393
531
|
function isEnvelope(value) {
|
|
394
532
|
return typeof value === "object" && value !== null && "__type" in value && typeof value.__type === "string";
|
|
395
533
|
}
|
|
@@ -471,7 +609,7 @@ function init(opts) {
|
|
|
471
609
|
logger,
|
|
472
610
|
emit,
|
|
473
611
|
conn,
|
|
474
|
-
memEntries: new MemoryStore(),
|
|
612
|
+
memEntries: new MemoryStore(opts.memoryMaxEntries),
|
|
475
613
|
memTagStates: /* @__PURE__ */ new Map(),
|
|
476
614
|
resolveNs: () => resolveBuildNamespace(opts.buildNamespace)
|
|
477
615
|
};
|
|
@@ -530,7 +668,7 @@ function normalizeTtlSeconds(data, ctx) {
|
|
|
530
668
|
return ONE_YEAR_SEC;
|
|
531
669
|
}
|
|
532
670
|
}
|
|
533
|
-
const revalidate = ctx?.cacheControl?.revalidate;
|
|
671
|
+
const revalidate = ctx?.cacheControl?.revalidate ?? ctx?.revalidate;
|
|
534
672
|
if (revalidate === false || revalidate === void 0) return ONE_YEAR_SEC;
|
|
535
673
|
if (typeof revalidate === "number" && revalidate > 0) {
|
|
536
674
|
return Math.min(Math.max(Math.ceil(revalidate), MIN_TTL_SEC), ONE_YEAR_SEC);
|
|
@@ -568,10 +706,49 @@ async function readTagStates(state, tags) {
|
|
|
568
706
|
var IncrementalRedisCacheHandler = class {
|
|
569
707
|
state;
|
|
570
708
|
revalidatedTags;
|
|
709
|
+
/**
|
|
710
|
+
* Request-scoped memo of Redis entry reads. Next.js instantiates this class
|
|
711
|
+
* per request (and calls resetRequestCache() at request boundaries when it
|
|
712
|
+
* reuses an instance), so entries live at most one request. Bounded by the
|
|
713
|
+
* number of distinct cache keys a single request touches.
|
|
714
|
+
*/
|
|
715
|
+
requestMemo = /* @__PURE__ */ new Map();
|
|
571
716
|
constructor(state, ctx) {
|
|
572
717
|
this.state = state;
|
|
573
718
|
this.revalidatedTags = normalizeTags(ctx?.revalidatedTags);
|
|
574
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Redis entry read, deduplicated per request: parallel RSC renders or
|
|
722
|
+
* repeated fetch() calls hitting the same cache key share one GET. The
|
|
723
|
+
* memoized promise never rejects — errors are converted to a miss (with
|
|
724
|
+
* the same timeout metric the inline path emitted before).
|
|
725
|
+
*/
|
|
726
|
+
readEntryDeduped(eKey) {
|
|
727
|
+
const memoized = this.requestMemo.get(eKey);
|
|
728
|
+
if (memoized) {
|
|
729
|
+
this.state.emit({ type: "cache.get.deduped" });
|
|
730
|
+
return memoized;
|
|
731
|
+
}
|
|
732
|
+
const read = withAbortSignal(
|
|
733
|
+
"incremental.get",
|
|
734
|
+
this.state.opts.abortTimeoutMs,
|
|
735
|
+
async () => {
|
|
736
|
+
const client = await this.state.conn.getOrConnect();
|
|
737
|
+
if (!client) return null;
|
|
738
|
+
return await decompressValue(await client.get(eKey));
|
|
739
|
+
}
|
|
740
|
+
).catch((err) => {
|
|
741
|
+
if (err instanceof CacheTimeoutError) {
|
|
742
|
+
this.state.emit({
|
|
743
|
+
type: "redis.timeout",
|
|
744
|
+
meta: { op: "incremental.get" }
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
return null;
|
|
748
|
+
});
|
|
749
|
+
this.requestMemo.set(eKey, read);
|
|
750
|
+
return read;
|
|
751
|
+
}
|
|
575
752
|
async get(cacheKey, ctx) {
|
|
576
753
|
try {
|
|
577
754
|
const requestedTags = normalizeTags([
|
|
@@ -583,24 +760,11 @@ var IncrementalRedisCacheHandler = class {
|
|
|
583
760
|
const useRedis = !useLocal && gateRedis(this.state);
|
|
584
761
|
let raw = null;
|
|
585
762
|
if (useRedis) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
async () => {
|
|
591
|
-
const client = await this.state.conn.getOrConnect();
|
|
592
|
-
if (!client) return null;
|
|
593
|
-
return await client.get(eKey);
|
|
594
|
-
}
|
|
595
|
-
);
|
|
596
|
-
} catch (err) {
|
|
597
|
-
if (err instanceof CacheTimeoutError) {
|
|
598
|
-
this.state.emit({ type: "redis.timeout", meta: { op: "incremental.get" } });
|
|
599
|
-
}
|
|
600
|
-
raw = null;
|
|
601
|
-
}
|
|
763
|
+
raw = await this.readEntryDeduped(eKey);
|
|
764
|
+
}
|
|
765
|
+
if (raw === null && (useLocal || this.state.opts.fallback !== "never")) {
|
|
766
|
+
raw = this.state.memEntries.get(eKey);
|
|
602
767
|
}
|
|
603
|
-
if (raw === null) raw = this.state.memEntries.get(eKey);
|
|
604
768
|
if (raw === null) {
|
|
605
769
|
this.state.emit({ type: "cache.miss" });
|
|
606
770
|
return null;
|
|
@@ -613,7 +777,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
613
777
|
return null;
|
|
614
778
|
}
|
|
615
779
|
const tagStates = await readTagStates(this.state, tags);
|
|
616
|
-
const isFetch = ctx?.kind === "FETCH";
|
|
780
|
+
const isFetch = ctx?.kind === "FETCH" || ctx?.kindHint?.toLowerCase() === "fetch" || typeof parsed.value === "object" && parsed.value !== null && parsed.value.kind === "FETCH";
|
|
617
781
|
if (tagStates.some((s) => isTagStateExpired(s, parsed.lastModified, isFetch))) {
|
|
618
782
|
this.state.emit({ type: "cache.miss", meta: { reason: "tag-state-expired" } });
|
|
619
783
|
return null;
|
|
@@ -636,6 +800,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
636
800
|
const record = { lastModified: Date.now(), value: data };
|
|
637
801
|
const serialized = serializeCacheRecord(record);
|
|
638
802
|
const eKey = entryKey(this.state, cacheKey);
|
|
803
|
+
this.requestMemo.delete(eKey);
|
|
639
804
|
const useRedis = !useLocal && gateRedis(this.state);
|
|
640
805
|
if (useRedis) {
|
|
641
806
|
try {
|
|
@@ -645,10 +810,19 @@ var IncrementalRedisCacheHandler = class {
|
|
|
645
810
|
async () => {
|
|
646
811
|
const client = await this.state.conn.getOrConnect();
|
|
647
812
|
if (!client) {
|
|
648
|
-
this.state.
|
|
813
|
+
if (this.state.opts.fallback !== "never") {
|
|
814
|
+
this.state.memEntries.set(eKey, serialized, ttl);
|
|
815
|
+
}
|
|
649
816
|
return;
|
|
650
817
|
}
|
|
651
|
-
await client.set(
|
|
818
|
+
await client.set(
|
|
819
|
+
eKey,
|
|
820
|
+
await compressValue(
|
|
821
|
+
serialized,
|
|
822
|
+
this.state.opts.rest.compression
|
|
823
|
+
),
|
|
824
|
+
{ EX: ttl }
|
|
825
|
+
);
|
|
652
826
|
}
|
|
653
827
|
);
|
|
654
828
|
this.state.emit({ type: "cache.set", meta: { backend: "redis" } });
|
|
@@ -659,8 +833,15 @@ var IncrementalRedisCacheHandler = class {
|
|
|
659
833
|
}
|
|
660
834
|
}
|
|
661
835
|
}
|
|
662
|
-
this.state.
|
|
663
|
-
|
|
836
|
+
if (useLocal || this.state.opts.fallback !== "never") {
|
|
837
|
+
this.state.memEntries.set(eKey, serialized, ttl);
|
|
838
|
+
this.state.emit({ type: "cache.set", meta: { backend: "memory" } });
|
|
839
|
+
} else {
|
|
840
|
+
this.state.emit({
|
|
841
|
+
type: "cache.set.failed",
|
|
842
|
+
meta: { reason: "no-fallback" }
|
|
843
|
+
});
|
|
844
|
+
}
|
|
664
845
|
} catch (err) {
|
|
665
846
|
this.state.logger.error("set() error", {
|
|
666
847
|
message: err.message
|
|
@@ -714,6 +895,7 @@ var IncrementalRedisCacheHandler = class {
|
|
|
714
895
|
}
|
|
715
896
|
}
|
|
716
897
|
resetRequestCache() {
|
|
898
|
+
this.requestMemo.clear();
|
|
717
899
|
}
|
|
718
900
|
};
|
|
719
901
|
function createIncrementalCacheHandler(opts) {
|