@leejpsd/nextjs-cache-handler 0.4.1 → 0.4.2
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 +16 -0
- package/README.md +41 -15
- package/dist/cache-components/index.cjs +59 -37
- package/dist/cache-components/index.cjs.map +1 -1
- package/dist/cache-components/index.d.cts +2 -2
- package/dist/cache-components/index.d.ts +2 -2
- package/dist/cache-components/index.js +59 -37
- package/dist/cache-components/index.js.map +1 -1
- package/dist/incremental/index.cjs.map +1 -1
- package/dist/incremental/index.d.cts +2 -2
- package/dist/incremental/index.d.ts +2 -2
- package/dist/incremental/index.js.map +1 -1
- package/dist/index.cjs +59 -37
- 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 +59 -37
- 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.d.cts +1 -1
- package/dist/otel/index.d.ts +1 -1
- package/dist/seed/index.d.cts +1 -1
- package/dist/seed/index.d.ts +1 -1
- package/dist/shared/client/adapter-ioredis.d.cts +1 -1
- package/dist/shared/client/adapter-ioredis.d.ts +1 -1
- package/dist/shared/client/adapter-redis.d.cts +1 -1
- package/dist/shared/client/adapter-redis.d.ts +1 -1
- package/dist/{types-C1vJ_sED.d.cts → types-YbDAllyp.d.cts} +7 -1
- package/dist/{types-C1vJ_sED.d.ts → types-YbDAllyp.d.ts} +7 -1
- package/package.json +7 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CacheComponentsHandler, createCacheComponentsHandler, partitionEntry, shouldServeStale } from './cache-components/index.cjs';
|
|
2
2
|
export { IncrementalCtx, IncrementalRedisCacheHandler, createIncrementalCacheHandler } from './incremental/index.cjs';
|
|
3
3
|
export { getMetricSnapshot } from './ops/index.cjs';
|
|
4
|
-
export { C as CacheComponentsEntry, c as CacheHandlerOptions, F as FallbackStrategy, I as IncrementalCacheData, L as Logger, M as MetricEmitter, d as MetricEvent, e as MetricEventType, a as RedisClientConfig, b as RedisClientFactory, R as RedisClientLike } from './types-
|
|
4
|
+
export { C as CacheComponentsEntry, c as CacheHandlerOptions, F as FallbackStrategy, I as IncrementalCacheData, L as Logger, M as MetricEmitter, d as MetricEvent, e as MetricEventType, a as RedisClientConfig, b as RedisClientFactory, R as RedisClientLike } from './types-YbDAllyp.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Timeout-bounded operation wrapper. Surrounds every Redis call so a hung
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CacheComponentsHandler, createCacheComponentsHandler, partitionEntry, shouldServeStale } from './cache-components/index.js';
|
|
2
2
|
export { IncrementalCtx, IncrementalRedisCacheHandler, createIncrementalCacheHandler } from './incremental/index.js';
|
|
3
3
|
export { getMetricSnapshot } from './ops/index.js';
|
|
4
|
-
export { C as CacheComponentsEntry, c as CacheHandlerOptions, F as FallbackStrategy, I as IncrementalCacheData, L as Logger, M as MetricEmitter, d as MetricEvent, e as MetricEventType, a as RedisClientConfig, b as RedisClientFactory, R as RedisClientLike } from './types-
|
|
4
|
+
export { C as CacheComponentsEntry, c as CacheHandlerOptions, F as FallbackStrategy, I as IncrementalCacheData, L as Logger, M as MetricEmitter, d as MetricEvent, e as MetricEventType, a as RedisClientConfig, b as RedisClientFactory, R as RedisClientLike } from './types-YbDAllyp.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Timeout-bounded operation wrapper. Surrounds every Redis call so a hung
|
package/dist/index.js
CHANGED
|
@@ -518,7 +518,7 @@ return 1
|
|
|
518
518
|
`;
|
|
519
519
|
|
|
520
520
|
// src/shared/lua/revalidate-hard.lua
|
|
521
|
-
var revalidate_hard_default = "-- revalidate-hard.lua\n--\n-- Atomically remove every entry referenced by a tag and stamp the tag's\n-- expiration marker. Used by
|
|
521
|
+
var revalidate_hard_default = "-- revalidate-hard.lua\n--\n-- Atomically remove every entry referenced by a tag and stamp the tag's\n-- expiration marker. Used by the hard invalidation path \u2014 `updateTags(tags)`\n-- with no durations (updateTag(), single-arg revalidateTag()) or an explicit\n-- `{ expire: 0 }`.\n--\n-- KEYS[1] : tag set key (e.g. \"next-cache:tag:posts\")\n-- KEYS[2] : tag-expiration marker key (e.g. \"next-cache:tag-expiration:posts\")\n-- ARGV[1] : marker value \"<staleMs>|<expiredMs>\" (hard: both equal)\n-- ARGV[2] : marker TTL in seconds (default: 1 day so cross-deployment\n-- readers can still observe the invalidation)\n--\n-- Returns: number of entries that were deleted.\n\nlocal tagKey = KEYS[1]\nlocal markerKey = KEYS[2]\nlocal marker = ARGV[1]\nlocal markerTtl = tonumber(ARGV[2])\n\nlocal entries = redis.call('SMEMBERS', tagKey)\nlocal deleted = 0\n\nif #entries > 0 then\n -- DEL accepts varargs; unpack the entry list.\n redis.call('DEL', unpack(entries))\n deleted = #entries\nend\n\n-- Drop the tag set itself; on next SET it'll be recreated by set-with-tags.\nredis.call('DEL', tagKey)\n\n-- Stamp the invalidation timestamp. Readers compare this against an entry's\n-- timestamp inside `getExpiration` to decide whether soft-tag-only entries\n-- should be considered stale.\nredis.call('SET', markerKey, marker, 'EX', markerTtl)\n\nreturn deleted\n";
|
|
522
522
|
|
|
523
523
|
// src/shared/lua/refresh-tag-lock.lua
|
|
524
524
|
var refresh_tag_lock_default = `-- refresh-tag-lock.lua
|
|
@@ -873,6 +873,23 @@ var DEFAULT_LOCK_TTL_SEC = 10;
|
|
|
873
873
|
var ONE_YEAR_SEC = 60 * 60 * 24 * 365;
|
|
874
874
|
var FALLBACK_TTL_SEC = 3600;
|
|
875
875
|
var TAG_EXP_MARKER_TTL_SEC = 60 * 60 * 24 * 7;
|
|
876
|
+
function encodeTagMarker(state) {
|
|
877
|
+
return `${state.s}|${state.e === Infinity ? "inf" : state.e}`;
|
|
878
|
+
}
|
|
879
|
+
function decodeTagMarker(raw) {
|
|
880
|
+
const s = Number.parseInt(raw, 10);
|
|
881
|
+
if (!Number.isFinite(s)) return null;
|
|
882
|
+
const sep = raw.indexOf("|");
|
|
883
|
+
if (sep === -1) return { s, e: s };
|
|
884
|
+
const tail = raw.slice(sep + 1);
|
|
885
|
+
if (tail === "inf") return { s, e: Infinity };
|
|
886
|
+
const e = Number.parseInt(tail, 10);
|
|
887
|
+
return { s, e: Number.isFinite(e) ? e : s };
|
|
888
|
+
}
|
|
889
|
+
function mergeTagState(map, tag, next) {
|
|
890
|
+
const prev = map.get(tag);
|
|
891
|
+
if (!prev || next.s > prev.s) map.set(tag, next);
|
|
892
|
+
}
|
|
876
893
|
function gateRedis(state) {
|
|
877
894
|
const fb = state.opts.fallback;
|
|
878
895
|
const ibp = state.opts.rest.isBuildPhase;
|
|
@@ -1057,21 +1074,17 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
1057
1074
|
const freshnessTags = softTags.length > 0 ? [...softTags, ...envelope.tags] : envelope.tags;
|
|
1058
1075
|
let tagStale = false;
|
|
1059
1076
|
if (freshnessTags.length > 0) {
|
|
1060
|
-
|
|
1077
|
+
const now = Date.now();
|
|
1061
1078
|
for (const t of freshnessTags) {
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
);
|
|
1066
|
-
if (
|
|
1067
|
-
}
|
|
1068
|
-
if (mostRecent > envelope.timestamp) {
|
|
1069
|
-
if (state.opts.staleWhileRevalidate && partition.freshness !== "expired") {
|
|
1070
|
-
tagStale = true;
|
|
1071
|
-
} else {
|
|
1079
|
+
const local = state.localTagTimestamps.get(t);
|
|
1080
|
+
const mem = state.memTagExp.get(t);
|
|
1081
|
+
const st = local && mem ? local.s >= mem.s ? local : mem : local ?? mem;
|
|
1082
|
+
if (!st || st.s <= envelope.timestamp) continue;
|
|
1083
|
+
if (now >= st.e || partition.freshness === "expired") {
|
|
1072
1084
|
state.emit({ type: "cache.miss", meta: { reason: "tag-invalidated" } });
|
|
1073
1085
|
return void 0;
|
|
1074
1086
|
}
|
|
1087
|
+
tagStale = true;
|
|
1075
1088
|
}
|
|
1076
1089
|
}
|
|
1077
1090
|
const freshness = tagStale ? "stale" : partition.freshness;
|
|
@@ -1098,15 +1111,13 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
1098
1111
|
meta: { freshness }
|
|
1099
1112
|
});
|
|
1100
1113
|
}
|
|
1101
|
-
const revalidateMs = Math.max(0, envelope.revalidate) * 1e3;
|
|
1102
|
-
const outTimestamp = tagStale ? Math.min(envelope.timestamp, Date.now() - revalidateMs - 1) : envelope.timestamp;
|
|
1103
1114
|
return {
|
|
1104
1115
|
value: bufferToStream(Buffer.from(envelope.value, "base64")),
|
|
1105
1116
|
tags: envelope.tags,
|
|
1106
1117
|
stale: envelope.stale,
|
|
1107
|
-
timestamp:
|
|
1118
|
+
timestamp: envelope.timestamp,
|
|
1108
1119
|
expire: envelope.expire,
|
|
1109
|
-
revalidate: envelope.revalidate
|
|
1120
|
+
revalidate: tagStale ? -1 : envelope.revalidate
|
|
1110
1121
|
};
|
|
1111
1122
|
}
|
|
1112
1123
|
async function setImpl(state, cacheKey, pendingEntry) {
|
|
@@ -1120,6 +1131,10 @@ async function setImpl(state, cacheKey, pendingEntry) {
|
|
|
1120
1131
|
state.emit({ type: "cache.set.failed", meta: { reason: "pending-rejected" } });
|
|
1121
1132
|
return;
|
|
1122
1133
|
}
|
|
1134
|
+
if (entry.expire === 0 && !process.env.__NEXT_DEV_SERVER) {
|
|
1135
|
+
state.emit({ type: "cache.set.skipped", meta: { reason: "dynamic-entry" } });
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1123
1138
|
let buf;
|
|
1124
1139
|
try {
|
|
1125
1140
|
buf = await readStreamFully(entry.value);
|
|
@@ -1225,11 +1240,13 @@ async function ensureTagSubscription(state) {
|
|
|
1225
1240
|
try {
|
|
1226
1241
|
const parsed = JSON.parse(message);
|
|
1227
1242
|
if (!Array.isArray(parsed.t) || typeof parsed.ts !== "number") return;
|
|
1243
|
+
const incoming = {
|
|
1244
|
+
s: parsed.ts,
|
|
1245
|
+
e: typeof parsed.e === "number" ? parsed.e : Infinity
|
|
1246
|
+
};
|
|
1228
1247
|
for (const tag of parsed.t) {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const prevMem = state.memTagExp.get(tag) ?? 0;
|
|
1232
|
-
if (parsed.ts > prevMem) state.memTagExp.set(tag, parsed.ts);
|
|
1248
|
+
mergeTagState(state.localTagTimestamps, tag, incoming);
|
|
1249
|
+
mergeTagState(state.memTagExp, tag, incoming);
|
|
1233
1250
|
}
|
|
1234
1251
|
pruneOldestByTimestamp(state.localTagTimestamps, MAX_LOCAL_TAG_ENTRIES);
|
|
1235
1252
|
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
@@ -1281,17 +1298,17 @@ async function refreshTagsImpl(state) {
|
|
|
1281
1298
|
for (let i = 0; i < keys.length; i += 1) {
|
|
1282
1299
|
const v = values[i];
|
|
1283
1300
|
if (!v) continue;
|
|
1284
|
-
const
|
|
1285
|
-
if (!
|
|
1301
|
+
const st = decodeTagMarker(v);
|
|
1302
|
+
if (!st) continue;
|
|
1286
1303
|
const key = keys[i];
|
|
1287
1304
|
if (key === void 0) continue;
|
|
1288
1305
|
const tag = stripExpPrefix(state, key);
|
|
1289
|
-
if (tag) fresh.set(tag,
|
|
1306
|
+
if (tag) fresh.set(tag, st);
|
|
1290
1307
|
}
|
|
1291
1308
|
}
|
|
1292
|
-
for (const [tag,
|
|
1293
|
-
if (
|
|
1294
|
-
fresh.set(tag,
|
|
1309
|
+
for (const [tag, st] of state.localTagTimestamps) {
|
|
1310
|
+
if (st.s >= scanStartMs && (fresh.get(tag)?.s ?? 0) < st.s) {
|
|
1311
|
+
fresh.set(tag, st);
|
|
1295
1312
|
}
|
|
1296
1313
|
}
|
|
1297
1314
|
state.localTagTimestamps = fresh;
|
|
@@ -1309,7 +1326,7 @@ function escapeMatch(s) {
|
|
|
1309
1326
|
var MAX_LOCAL_TAG_ENTRIES = 1e4;
|
|
1310
1327
|
function pruneOldestByTimestamp(map, maxEntries) {
|
|
1311
1328
|
if (map.size <= maxEntries) return;
|
|
1312
|
-
const byAge = [...map.entries()].sort((a, b) => a[1] - b[1]);
|
|
1329
|
+
const byAge = [...map.entries()].sort((a, b) => a[1].s - b[1].s);
|
|
1313
1330
|
const drop = map.size - maxEntries;
|
|
1314
1331
|
for (let i = 0; i < drop; i += 1) {
|
|
1315
1332
|
const entry = byAge[i];
|
|
@@ -1326,9 +1343,9 @@ async function getExpirationImpl(state, tags) {
|
|
|
1326
1343
|
let max = 0;
|
|
1327
1344
|
for (const t of tags) {
|
|
1328
1345
|
const local = state.localTagTimestamps.get(t);
|
|
1329
|
-
if (local
|
|
1346
|
+
if (local && Number.isFinite(local.e) && local.e > max) max = local.e;
|
|
1330
1347
|
const mem = state.memTagExp.get(t);
|
|
1331
|
-
if (mem
|
|
1348
|
+
if (mem && Number.isFinite(mem.e) && mem.e > max) max = mem.e;
|
|
1332
1349
|
}
|
|
1333
1350
|
if (max > 0) {
|
|
1334
1351
|
state.emit({ type: "tag.expiration.read", meta: { source: "local", count: tags.length } });
|
|
@@ -1349,8 +1366,8 @@ async function getExpirationImpl(state, tags) {
|
|
|
1349
1366
|
let m = 0;
|
|
1350
1367
|
for (const v of values) {
|
|
1351
1368
|
if (!v) continue;
|
|
1352
|
-
const
|
|
1353
|
-
if (Number.isFinite(
|
|
1369
|
+
const st = decodeTagMarker(v);
|
|
1370
|
+
if (st && Number.isFinite(st.e) && st.e > m) m = st.e;
|
|
1354
1371
|
}
|
|
1355
1372
|
return m;
|
|
1356
1373
|
}
|
|
@@ -1361,11 +1378,14 @@ async function getExpirationImpl(state, tags) {
|
|
|
1361
1378
|
}
|
|
1362
1379
|
async function updateTagsImpl(state, tags, durations) {
|
|
1363
1380
|
if (tags.length === 0) return;
|
|
1364
|
-
const isHardExpire = durations
|
|
1381
|
+
const isHardExpire = durations === void 0 || durations.expire === 0;
|
|
1365
1382
|
const now = Date.now();
|
|
1383
|
+
const deadline = isHardExpire ? now : durations.expire !== void 0 && Number.isFinite(durations.expire) ? now + durations.expire * 1e3 : Infinity;
|
|
1384
|
+
const tagState = { s: now, e: deadline };
|
|
1385
|
+
const marker = encodeTagMarker(tagState);
|
|
1366
1386
|
for (const t of tags) {
|
|
1367
|
-
state.localTagTimestamps
|
|
1368
|
-
state.memTagExp
|
|
1387
|
+
mergeTagState(state.localTagTimestamps, t, tagState);
|
|
1388
|
+
mergeTagState(state.memTagExp, t, tagState);
|
|
1369
1389
|
}
|
|
1370
1390
|
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
1371
1391
|
if (!gateRedis(state)) {
|
|
@@ -1403,8 +1423,8 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1403
1423
|
client,
|
|
1404
1424
|
"revalidateHard",
|
|
1405
1425
|
[tagKey(state, t), tagExpKey(state, t)],
|
|
1406
|
-
[
|
|
1407
|
-
) : client.set(tagExpKey(state, t),
|
|
1426
|
+
[marker, String(TAG_EXP_MARKER_TTL_SEC)]
|
|
1427
|
+
) : client.set(tagExpKey(state, t), marker, {
|
|
1408
1428
|
EX: TAG_EXP_MARKER_TTL_SEC
|
|
1409
1429
|
})
|
|
1410
1430
|
)
|
|
@@ -1420,7 +1440,9 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1420
1440
|
const client = await state.conn.getOrConnect();
|
|
1421
1441
|
await client?.publish?.(
|
|
1422
1442
|
invalChannel(state),
|
|
1423
|
-
JSON.stringify(
|
|
1443
|
+
JSON.stringify(
|
|
1444
|
+
deadline === Infinity ? { t: tags, ts: now } : { t: tags, ts: now, e: deadline }
|
|
1445
|
+
)
|
|
1424
1446
|
);
|
|
1425
1447
|
} catch {
|
|
1426
1448
|
}
|