@leejpsd/nextjs-cache-handler 0.2.0 → 0.3.1
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 +67 -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 +27 -5
|
@@ -1,13 +1,12 @@
|
|
|
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';
|
|
1
6
|
import { createHash } from 'crypto';
|
|
2
7
|
|
|
3
8
|
var __defProp = Object.defineProperty;
|
|
4
9
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
-
}) : x)(function(x) {
|
|
8
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
-
});
|
|
11
10
|
var __esm = (fn, res) => function __init() {
|
|
12
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
12
|
};
|
|
@@ -15,6 +14,27 @@ var __export = (target, all) => {
|
|
|
15
14
|
for (var name in all)
|
|
16
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
16
|
};
|
|
17
|
+
var init_esm_shims = __esm({
|
|
18
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
function nodeRequire(id) {
|
|
22
|
+
try {
|
|
23
|
+
return packageRequire(id);
|
|
24
|
+
} catch {
|
|
25
|
+
return cwdRequire(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var packageRequire, cwdRequire;
|
|
29
|
+
var init_node_require = __esm({
|
|
30
|
+
"src/shared/node-require.ts"() {
|
|
31
|
+
init_esm_shims();
|
|
32
|
+
packageRequire = createRequire(import.meta.url);
|
|
33
|
+
cwdRequire = createRequire(
|
|
34
|
+
path2.join(process.cwd(), "__resolve-anchor__.js")
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
18
38
|
|
|
19
39
|
// src/shared/client/adapter-redis.ts
|
|
20
40
|
var adapter_redis_exports = {};
|
|
@@ -26,7 +46,7 @@ function adaptRedisV5(client) {
|
|
|
26
46
|
return client;
|
|
27
47
|
}
|
|
28
48
|
function createRedisV5Client(config) {
|
|
29
|
-
const { createClient } =
|
|
49
|
+
const { createClient } = nodeRequire("redis");
|
|
30
50
|
const useTls = config.tls ?? config.url.startsWith("rediss://");
|
|
31
51
|
const opts = {
|
|
32
52
|
url: config.url,
|
|
@@ -41,6 +61,8 @@ function createRedisV5Client(config) {
|
|
|
41
61
|
}
|
|
42
62
|
var init_adapter_redis = __esm({
|
|
43
63
|
"src/shared/client/adapter-redis.ts"() {
|
|
64
|
+
init_esm_shims();
|
|
65
|
+
init_node_require();
|
|
44
66
|
}
|
|
45
67
|
});
|
|
46
68
|
|
|
@@ -50,7 +72,8 @@ __export(adapter_ioredis_exports, {
|
|
|
50
72
|
adaptCluster: () => adaptCluster,
|
|
51
73
|
adaptIoredis: () => adaptIoredis,
|
|
52
74
|
createIoredisClient: () => createIoredisClient,
|
|
53
|
-
createIoredisCluster: () => createIoredisCluster
|
|
75
|
+
createIoredisCluster: () => createIoredisCluster,
|
|
76
|
+
createIoredisSentinel: () => createIoredisSentinel
|
|
54
77
|
});
|
|
55
78
|
function adapt(client, isClusterClient = false) {
|
|
56
79
|
return {
|
|
@@ -133,6 +156,9 @@ function adapt(client, isClusterClient = false) {
|
|
|
133
156
|
on: (event, listener) => {
|
|
134
157
|
client.on(event, listener);
|
|
135
158
|
return client;
|
|
159
|
+
},
|
|
160
|
+
dispose: () => {
|
|
161
|
+
client.disconnect();
|
|
136
162
|
}
|
|
137
163
|
};
|
|
138
164
|
}
|
|
@@ -143,18 +169,36 @@ function adaptCluster(cluster) {
|
|
|
143
169
|
return adapt(cluster, true);
|
|
144
170
|
}
|
|
145
171
|
function createIoredisClient(config) {
|
|
146
|
-
const IORedis =
|
|
172
|
+
const IORedis = nodeRequire("ioredis");
|
|
147
173
|
const Ctor = IORedis.default ?? IORedis;
|
|
148
174
|
return new Ctor(config.url, {
|
|
149
175
|
password: config.password,
|
|
150
176
|
connectTimeout: config.connectTimeout ?? 1500,
|
|
151
177
|
tls: config.tls ? {} : void 0,
|
|
152
178
|
lazyConnect: true,
|
|
153
|
-
maxRetriesPerRequest: 1
|
|
179
|
+
maxRetriesPerRequest: 1,
|
|
180
|
+
// Batch same-tick commands into one RTT (matches redis@5's default
|
|
181
|
+
// behavior) — makes Promise.all fan-outs a single pipeline.
|
|
182
|
+
enableAutoPipelining: true
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function createIoredisSentinel(config) {
|
|
186
|
+
const IORedis = nodeRequire("ioredis");
|
|
187
|
+
const Ctor = IORedis.default ?? IORedis;
|
|
188
|
+
return new Ctor({
|
|
189
|
+
sentinels: config.sentinels,
|
|
190
|
+
name: config.name,
|
|
191
|
+
password: config.password,
|
|
192
|
+
sentinelPassword: config.sentinelPassword,
|
|
193
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
194
|
+
tls: config.tls ? {} : void 0,
|
|
195
|
+
lazyConnect: true,
|
|
196
|
+
maxRetriesPerRequest: 1,
|
|
197
|
+
enableAutoPipelining: true
|
|
154
198
|
});
|
|
155
199
|
}
|
|
156
200
|
function createIoredisCluster(config) {
|
|
157
|
-
const IORedis =
|
|
201
|
+
const IORedis = nodeRequire("ioredis");
|
|
158
202
|
const ClusterCtor = IORedis.Cluster ?? IORedis.default.Cluster;
|
|
159
203
|
return new ClusterCtor(config.nodes, {
|
|
160
204
|
redisOptions: {
|
|
@@ -162,15 +206,26 @@ function createIoredisCluster(config) {
|
|
|
162
206
|
tls: config.tls ? {} : void 0
|
|
163
207
|
},
|
|
164
208
|
enableReadyCheck: true,
|
|
165
|
-
lazyConnect: true
|
|
209
|
+
lazyConnect: true,
|
|
210
|
+
// Same-slot commands issued in the same tick share one pipeline.
|
|
211
|
+
enableAutoPipelining: true
|
|
166
212
|
});
|
|
167
213
|
}
|
|
168
214
|
var init_adapter_ioredis = __esm({
|
|
169
215
|
"src/shared/client/adapter-ioredis.ts"() {
|
|
216
|
+
init_esm_shims();
|
|
217
|
+
init_node_require();
|
|
170
218
|
}
|
|
171
219
|
});
|
|
172
220
|
|
|
221
|
+
// src/cache-components/index.ts
|
|
222
|
+
init_esm_shims();
|
|
223
|
+
|
|
224
|
+
// src/cache-components/handler.ts
|
|
225
|
+
init_esm_shims();
|
|
226
|
+
|
|
173
227
|
// src/shared/abort.ts
|
|
228
|
+
init_esm_shims();
|
|
174
229
|
var CacheTimeoutError = class extends Error {
|
|
175
230
|
constructor(opName, ms) {
|
|
176
231
|
super(`[@leejpsd/nextjs-cache-handler] ${opName} timed out after ${ms}ms`);
|
|
@@ -202,7 +257,41 @@ async function withAbortSignal(opName, ms, fn) {
|
|
|
202
257
|
}
|
|
203
258
|
}
|
|
204
259
|
|
|
260
|
+
// src/shared/compress.ts
|
|
261
|
+
init_esm_shims();
|
|
262
|
+
var gzipAsync = promisify(gzip);
|
|
263
|
+
var gunzipAsync = promisify(gunzip);
|
|
264
|
+
var brotliCompressAsync = promisify(brotliCompress);
|
|
265
|
+
var brotliDecompressAsync = promisify(brotliDecompress);
|
|
266
|
+
var GZIP_MARKER = "__ncgz__:";
|
|
267
|
+
var BROTLI_MARKER = "__ncbr__:";
|
|
268
|
+
var MIN_COMPRESS_BYTES = 1024;
|
|
269
|
+
var BROTLI_OPTS = {
|
|
270
|
+
params: { [constants.BROTLI_PARAM_QUALITY]: 5 }
|
|
271
|
+
};
|
|
272
|
+
async function compressValue(value, algo) {
|
|
273
|
+
if (!algo || Buffer.byteLength(value) < MIN_COMPRESS_BYTES) return value;
|
|
274
|
+
const input = Buffer.from(value);
|
|
275
|
+
if (algo === "gzip") {
|
|
276
|
+
return GZIP_MARKER + (await gzipAsync(input)).toString("base64");
|
|
277
|
+
}
|
|
278
|
+
return BROTLI_MARKER + (await brotliCompressAsync(input, BROTLI_OPTS)).toString("base64");
|
|
279
|
+
}
|
|
280
|
+
async function decompressValue(value) {
|
|
281
|
+
if (value === null) return null;
|
|
282
|
+
if (value.startsWith(GZIP_MARKER)) {
|
|
283
|
+
const buf = Buffer.from(value.slice(GZIP_MARKER.length), "base64");
|
|
284
|
+
return (await gunzipAsync(buf)).toString("utf8");
|
|
285
|
+
}
|
|
286
|
+
if (value.startsWith(BROTLI_MARKER)) {
|
|
287
|
+
const buf = Buffer.from(value.slice(BROTLI_MARKER.length), "base64");
|
|
288
|
+
return (await brotliDecompressAsync(buf)).toString("utf8");
|
|
289
|
+
}
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
|
|
205
293
|
// src/shared/client/index.ts
|
|
294
|
+
init_esm_shims();
|
|
206
295
|
async function buildClient(source) {
|
|
207
296
|
if (typeof source === "function") {
|
|
208
297
|
return await source();
|
|
@@ -220,6 +309,10 @@ async function buildClient(source) {
|
|
|
220
309
|
const { adaptCluster: adaptCluster2, createIoredisCluster: createIoredisCluster2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
221
310
|
return adaptCluster2(createIoredisCluster2(source));
|
|
222
311
|
}
|
|
312
|
+
case "sentinel": {
|
|
313
|
+
const { adaptIoredis: adaptIoredis2, createIoredisSentinel: createIoredisSentinel2 } = await Promise.resolve().then(() => (init_adapter_ioredis(), adapter_ioredis_exports));
|
|
314
|
+
return adaptIoredis2(createIoredisSentinel2(source));
|
|
315
|
+
}
|
|
223
316
|
default: {
|
|
224
317
|
const exhaustive = source;
|
|
225
318
|
throw new Error(
|
|
@@ -229,28 +322,45 @@ async function buildClient(source) {
|
|
|
229
322
|
}
|
|
230
323
|
}
|
|
231
324
|
var ConnectionManager = class {
|
|
232
|
-
constructor(source, onError) {
|
|
325
|
+
constructor(source, onError, retry = {}) {
|
|
233
326
|
this.source = source;
|
|
234
327
|
this.onError = onError;
|
|
328
|
+
this.baseCooldownMs = retry.baseCooldownMs ?? 1e3;
|
|
329
|
+
this.maxCooldownMs = retry.maxCooldownMs ?? 3e4;
|
|
235
330
|
}
|
|
236
331
|
source;
|
|
237
332
|
onError;
|
|
238
333
|
client = null;
|
|
239
334
|
connecting = null;
|
|
240
|
-
|
|
335
|
+
consecutiveFailures = 0;
|
|
336
|
+
lastFailureAt = 0;
|
|
337
|
+
baseCooldownMs;
|
|
338
|
+
maxCooldownMs;
|
|
339
|
+
cooldownMs() {
|
|
340
|
+
if (this.consecutiveFailures === 0) return 0;
|
|
341
|
+
return Math.min(
|
|
342
|
+
this.maxCooldownMs,
|
|
343
|
+
this.baseCooldownMs * 2 ** (this.consecutiveFailures - 1)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
241
346
|
async getOrConnect() {
|
|
242
347
|
if (this.client?.isOpen) return this.client;
|
|
243
|
-
if (this.failed) return null;
|
|
244
348
|
if (this.connecting) return this.connecting;
|
|
349
|
+
if (Date.now() - this.lastFailureAt < this.cooldownMs()) return null;
|
|
350
|
+
const stale = this.client;
|
|
351
|
+
this.client = null;
|
|
245
352
|
this.connecting = (async () => {
|
|
353
|
+
disposeQuietly(stale);
|
|
246
354
|
try {
|
|
247
355
|
const c = await buildClient(this.source);
|
|
248
356
|
c.on("error", (err) => this.onError(err));
|
|
249
357
|
if (!c.isOpen) await c.connect();
|
|
250
358
|
this.client = c;
|
|
359
|
+
this.consecutiveFailures = 0;
|
|
251
360
|
return c;
|
|
252
361
|
} catch (err) {
|
|
253
|
-
this.
|
|
362
|
+
this.consecutiveFailures += 1;
|
|
363
|
+
this.lastFailureAt = Date.now();
|
|
254
364
|
this.onError(err);
|
|
255
365
|
return null;
|
|
256
366
|
} finally {
|
|
@@ -260,13 +370,24 @@ var ConnectionManager = class {
|
|
|
260
370
|
return this.connecting;
|
|
261
371
|
}
|
|
262
372
|
reset() {
|
|
373
|
+
disposeQuietly(this.client);
|
|
263
374
|
this.client = null;
|
|
264
375
|
this.connecting = null;
|
|
265
|
-
this.
|
|
376
|
+
this.consecutiveFailures = 0;
|
|
377
|
+
this.lastFailureAt = 0;
|
|
266
378
|
}
|
|
267
379
|
};
|
|
380
|
+
function disposeQuietly(client) {
|
|
381
|
+
if (!client) return;
|
|
382
|
+
const c = client;
|
|
383
|
+
try {
|
|
384
|
+
(c.dispose ?? c.destroy ?? c.disconnect)?.call(c);
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
}
|
|
268
388
|
|
|
269
389
|
// src/shared/logger.ts
|
|
390
|
+
init_esm_shims();
|
|
270
391
|
var defaultLogger = {
|
|
271
392
|
debug: () => {
|
|
272
393
|
},
|
|
@@ -282,6 +403,9 @@ var defaultLogger = {
|
|
|
282
403
|
}
|
|
283
404
|
};
|
|
284
405
|
|
|
406
|
+
// src/shared/lua/index.ts
|
|
407
|
+
init_esm_shims();
|
|
408
|
+
|
|
285
409
|
// src/shared/lua/set-with-tags.lua
|
|
286
410
|
var set_with_tags_default = `-- set-with-tags.lua
|
|
287
411
|
--
|
|
@@ -385,13 +509,19 @@ async function execLuaScript(client, name, keys, args) {
|
|
|
385
509
|
}
|
|
386
510
|
|
|
387
511
|
// src/shared/memory-fallback.ts
|
|
512
|
+
init_esm_shims();
|
|
388
513
|
var MAX_TIMER_MS = 2147483647;
|
|
389
514
|
function clampTimerMs(ms) {
|
|
390
515
|
return Math.min(Math.max(0, ms), MAX_TIMER_MS);
|
|
391
516
|
}
|
|
517
|
+
var DEFAULT_MAX_ENTRIES = 1e3;
|
|
392
518
|
var MemoryStore = class {
|
|
393
519
|
kv = /* @__PURE__ */ new Map();
|
|
394
520
|
timers = /* @__PURE__ */ new Map();
|
|
521
|
+
maxEntries;
|
|
522
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
523
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
524
|
+
}
|
|
395
525
|
set(key, value, ttlSeconds) {
|
|
396
526
|
this.delete(key);
|
|
397
527
|
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
@@ -401,6 +531,10 @@ var MemoryStore = class {
|
|
|
401
531
|
timer.unref();
|
|
402
532
|
}
|
|
403
533
|
this.timers.set(key, timer);
|
|
534
|
+
while (this.kv.size > this.maxEntries) {
|
|
535
|
+
const oldest = this.kv.keys().next().value;
|
|
536
|
+
this.delete(oldest);
|
|
537
|
+
}
|
|
404
538
|
}
|
|
405
539
|
get(key) {
|
|
406
540
|
const entry = this.kv.get(key);
|
|
@@ -409,6 +543,8 @@ var MemoryStore = class {
|
|
|
409
543
|
this.delete(key);
|
|
410
544
|
return null;
|
|
411
545
|
}
|
|
546
|
+
this.kv.delete(key);
|
|
547
|
+
this.kv.set(key, entry);
|
|
412
548
|
return entry.value;
|
|
413
549
|
}
|
|
414
550
|
delete(key) {
|
|
@@ -443,13 +579,19 @@ var MemorySetStore = class {
|
|
|
443
579
|
sets = /* @__PURE__ */ new Map();
|
|
444
580
|
expiresAt = /* @__PURE__ */ new Map();
|
|
445
581
|
timers = /* @__PURE__ */ new Map();
|
|
582
|
+
maxSets;
|
|
583
|
+
constructor(maxSets = DEFAULT_MAX_ENTRIES) {
|
|
584
|
+
this.maxSets = Math.max(1, maxSets);
|
|
585
|
+
}
|
|
446
586
|
sAdd(key, member) {
|
|
447
587
|
const members = Array.isArray(member) ? member : [member];
|
|
448
588
|
let set = this.sets.get(key);
|
|
449
589
|
if (!set) {
|
|
450
590
|
set = /* @__PURE__ */ new Set();
|
|
451
|
-
|
|
591
|
+
} else {
|
|
592
|
+
this.sets.delete(key);
|
|
452
593
|
}
|
|
594
|
+
this.sets.set(key, set);
|
|
453
595
|
let added = 0;
|
|
454
596
|
for (const m of members) {
|
|
455
597
|
if (!set.has(m)) {
|
|
@@ -457,6 +599,10 @@ var MemorySetStore = class {
|
|
|
457
599
|
added += 1;
|
|
458
600
|
}
|
|
459
601
|
}
|
|
602
|
+
while (this.sets.size > this.maxSets) {
|
|
603
|
+
const oldest = this.sets.keys().next().value;
|
|
604
|
+
this.delete(oldest);
|
|
605
|
+
}
|
|
460
606
|
return added;
|
|
461
607
|
}
|
|
462
608
|
sMembers(key) {
|
|
@@ -509,6 +655,7 @@ var MemorySetStore = class {
|
|
|
509
655
|
};
|
|
510
656
|
|
|
511
657
|
// src/shared/metrics.ts
|
|
658
|
+
init_esm_shims();
|
|
512
659
|
var counters = /* @__PURE__ */ new Map();
|
|
513
660
|
function bump(event) {
|
|
514
661
|
const existing = counters.get(event.type);
|
|
@@ -537,6 +684,7 @@ function createEmitter(onMetric) {
|
|
|
537
684
|
}
|
|
538
685
|
|
|
539
686
|
// src/shared/namespace.ts
|
|
687
|
+
init_esm_shims();
|
|
540
688
|
var FALLBACK = "unversioned";
|
|
541
689
|
function resolveBuildNamespace(override) {
|
|
542
690
|
if (override !== void 0) {
|
|
@@ -550,6 +698,7 @@ function buildKey(prefix, namespace, rest, hashTag = false) {
|
|
|
550
698
|
}
|
|
551
699
|
|
|
552
700
|
// src/shared/build-phase.ts
|
|
701
|
+
init_esm_shims();
|
|
553
702
|
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
554
703
|
function isBuildPhase() {
|
|
555
704
|
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD;
|
|
@@ -562,6 +711,7 @@ function shouldUseRedis(opts) {
|
|
|
562
711
|
}
|
|
563
712
|
|
|
564
713
|
// src/cache-components/serialize.ts
|
|
714
|
+
init_esm_shims();
|
|
565
715
|
async function readStreamFully(stream) {
|
|
566
716
|
const reader = stream.getReader();
|
|
567
717
|
const chunks = [];
|
|
@@ -604,6 +754,7 @@ function decodeEnvelope(raw) {
|
|
|
604
754
|
}
|
|
605
755
|
|
|
606
756
|
// src/cache-components/swr.ts
|
|
757
|
+
init_esm_shims();
|
|
607
758
|
var SECOND_MS = 1e3;
|
|
608
759
|
function partitionEntry(entry, now = Date.now()) {
|
|
609
760
|
const ageMs = now - entry.timestamp;
|
|
@@ -662,8 +813,8 @@ function init(opts) {
|
|
|
662
813
|
logger,
|
|
663
814
|
emit,
|
|
664
815
|
conn,
|
|
665
|
-
memEntries: new MemoryStore(),
|
|
666
|
-
memTags: new MemorySetStore(),
|
|
816
|
+
memEntries: new MemoryStore(opts.memoryMaxEntries),
|
|
817
|
+
memTags: new MemorySetStore(opts.memoryMaxEntries),
|
|
667
818
|
memTagExp: /* @__PURE__ */ new Map(),
|
|
668
819
|
localTagTimestamps: /* @__PURE__ */ new Map(),
|
|
669
820
|
resolveNs: () => resolveBuildNamespace(opts.buildNamespace),
|
|
@@ -780,6 +931,12 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
780
931
|
state.emit({ type: "cache.miss", ms: Date.now() - start });
|
|
781
932
|
return void 0;
|
|
782
933
|
}
|
|
934
|
+
try {
|
|
935
|
+
raw = await decompressValue(raw);
|
|
936
|
+
} catch {
|
|
937
|
+
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
938
|
+
return void 0;
|
|
939
|
+
}
|
|
783
940
|
envelope = decodeEnvelope(raw);
|
|
784
941
|
if (!envelope) {
|
|
785
942
|
state.emit({ type: "cache.miss", meta: { reason: "decode-error" } });
|
|
@@ -908,10 +1065,14 @@ async function setImpl(state, cacheKey, pendingEntry) {
|
|
|
908
1065
|
}
|
|
909
1066
|
return;
|
|
910
1067
|
}
|
|
1068
|
+
const wirePayload = await compressValue(
|
|
1069
|
+
payload,
|
|
1070
|
+
state.opts.rest.compression
|
|
1071
|
+
);
|
|
911
1072
|
if (entry.tags.length === 0) {
|
|
912
|
-
await client.set(eKey,
|
|
1073
|
+
await client.set(eKey, wirePayload, { EX: ttl });
|
|
913
1074
|
} else {
|
|
914
|
-
await luaSetWithTags(client, state, eKey,
|
|
1075
|
+
await luaSetWithTags(client, state, eKey, wirePayload, ttl, entry.tags);
|
|
915
1076
|
}
|
|
916
1077
|
}
|
|
917
1078
|
);
|
|
@@ -947,28 +1108,36 @@ async function refreshTagsImpl(state) {
|
|
|
947
1108
|
async () => {
|
|
948
1109
|
const client = await state.conn.getOrConnect();
|
|
949
1110
|
if (!client) return;
|
|
950
|
-
const
|
|
951
|
-
const
|
|
1111
|
+
const ns = state.opts.hashTag ? `{${state.resolveNs()}}` : state.resolveNs();
|
|
1112
|
+
const pattern = `${escapeMatch(
|
|
1113
|
+
`${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`
|
|
1114
|
+
)}*`;
|
|
1115
|
+
const scanStartMs = Date.now();
|
|
1116
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
952
1117
|
for await (const chunk of client.scanIterator({
|
|
953
1118
|
MATCH: pattern,
|
|
954
1119
|
COUNT: 100
|
|
955
1120
|
})) {
|
|
956
1121
|
const keys = Array.isArray(chunk) ? chunk.map(String) : [String(chunk)];
|
|
957
|
-
|
|
958
|
-
|
|
1122
|
+
if (keys.length === 0) continue;
|
|
1123
|
+
const values = await client.mGet(keys);
|
|
1124
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
1125
|
+
const v = values[i];
|
|
1126
|
+
if (!v) continue;
|
|
1127
|
+
const ts = Number.parseInt(v, 10);
|
|
1128
|
+
if (!Number.isFinite(ts)) continue;
|
|
1129
|
+
const key = keys[i];
|
|
1130
|
+
if (key === void 0) continue;
|
|
1131
|
+
const tag = stripExpPrefix(state, key);
|
|
1132
|
+
if (tag) fresh.set(tag, ts);
|
|
1133
|
+
}
|
|
959
1134
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
if (!v) continue;
|
|
965
|
-
const ts = Number.parseInt(v, 10);
|
|
966
|
-
if (!Number.isFinite(ts)) continue;
|
|
967
|
-
const key = seen[i];
|
|
968
|
-
if (key === void 0) continue;
|
|
969
|
-
const tag = stripExpPrefix(state, key);
|
|
970
|
-
if (tag) state.localTagTimestamps.set(tag, ts);
|
|
1135
|
+
for (const [tag, ts] of state.localTagTimestamps) {
|
|
1136
|
+
if (ts >= scanStartMs && (fresh.get(tag) ?? 0) < ts) {
|
|
1137
|
+
fresh.set(tag, ts);
|
|
1138
|
+
}
|
|
971
1139
|
}
|
|
1140
|
+
state.localTagTimestamps = fresh;
|
|
972
1141
|
}
|
|
973
1142
|
);
|
|
974
1143
|
} catch (err) {
|
|
@@ -977,6 +1146,19 @@ async function refreshTagsImpl(state) {
|
|
|
977
1146
|
});
|
|
978
1147
|
}
|
|
979
1148
|
}
|
|
1149
|
+
function escapeMatch(s) {
|
|
1150
|
+
return s.replace(/[\\*?[\]]/g, "\\$&");
|
|
1151
|
+
}
|
|
1152
|
+
var MAX_LOCAL_TAG_ENTRIES = 1e4;
|
|
1153
|
+
function pruneOldestByTimestamp(map, maxEntries) {
|
|
1154
|
+
if (map.size <= maxEntries) return;
|
|
1155
|
+
const byAge = [...map.entries()].sort((a, b) => a[1] - b[1]);
|
|
1156
|
+
const drop = map.size - maxEntries;
|
|
1157
|
+
for (let i = 0; i < drop; i += 1) {
|
|
1158
|
+
const entry = byAge[i];
|
|
1159
|
+
if (entry) map.delete(entry[0]);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
980
1162
|
function stripExpPrefix(state, fullKey) {
|
|
981
1163
|
const ns = state.resolveNs();
|
|
982
1164
|
const prefix = state.opts.hashTag ? `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}{${ns}}:` : `${state.opts.keyPrefix}${TAG_EXP_SUFFIX}${ns}:`;
|
|
@@ -1028,6 +1210,7 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1028
1210
|
state.localTagTimestamps.set(t, now);
|
|
1029
1211
|
state.memTagExp.set(t, now);
|
|
1030
1212
|
}
|
|
1213
|
+
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
1031
1214
|
if (!gateRedis(state)) {
|
|
1032
1215
|
if (isHardExpire) {
|
|
1033
1216
|
for (const t of tags) {
|
|
@@ -1057,20 +1240,18 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1057
1240
|
}
|
|
1058
1241
|
return;
|
|
1059
1242
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1243
|
+
await Promise.all(
|
|
1244
|
+
tags.map(
|
|
1245
|
+
(t) => isHardExpire ? execLuaScript(
|
|
1063
1246
|
client,
|
|
1064
1247
|
"revalidateHard",
|
|
1065
1248
|
[tagKey(state, t), tagExpKey(state, t)],
|
|
1066
1249
|
[String(now), String(TAG_EXP_MARKER_TTL_SEC)]
|
|
1067
|
-
)
|
|
1068
|
-
} else {
|
|
1069
|
-
await client.set(tagExpKey(state, t), String(now), {
|
|
1250
|
+
) : client.set(tagExpKey(state, t), String(now), {
|
|
1070
1251
|
EX: TAG_EXP_MARKER_TTL_SEC
|
|
1071
|
-
})
|
|
1072
|
-
|
|
1073
|
-
|
|
1252
|
+
})
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1074
1255
|
}
|
|
1075
1256
|
);
|
|
1076
1257
|
state.emit({
|