@leejpsd/nextjs-cache-handler 0.4.0 → 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 +26 -0
- package/README.md +46 -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/cli/index.cjs +4 -3
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/incremental/index.cjs +4 -3
- 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 +4 -3
- package/dist/incremental/index.js.map +1 -1
- package/dist/index.cjs +63 -40
- 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 +63 -40
- 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.cjs +4 -3
- package/dist/seed/index.cjs.map +1 -1
- package/dist/seed/index.d.cts +1 -1
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +4 -3
- package/dist/seed/index.js.map +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.cjs
CHANGED
|
@@ -528,7 +528,7 @@ return 1
|
|
|
528
528
|
`;
|
|
529
529
|
|
|
530
530
|
// src/shared/lua/revalidate-hard.lua
|
|
531
|
-
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
|
|
531
|
+
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";
|
|
532
532
|
|
|
533
533
|
// src/shared/lua/refresh-tag-lock.lua
|
|
534
534
|
var refresh_tag_lock_default = `-- refresh-tag-lock.lua
|
|
@@ -883,6 +883,23 @@ var DEFAULT_LOCK_TTL_SEC = 10;
|
|
|
883
883
|
var ONE_YEAR_SEC = 60 * 60 * 24 * 365;
|
|
884
884
|
var FALLBACK_TTL_SEC = 3600;
|
|
885
885
|
var TAG_EXP_MARKER_TTL_SEC = 60 * 60 * 24 * 7;
|
|
886
|
+
function encodeTagMarker(state) {
|
|
887
|
+
return `${state.s}|${state.e === Infinity ? "inf" : state.e}`;
|
|
888
|
+
}
|
|
889
|
+
function decodeTagMarker(raw) {
|
|
890
|
+
const s = Number.parseInt(raw, 10);
|
|
891
|
+
if (!Number.isFinite(s)) return null;
|
|
892
|
+
const sep = raw.indexOf("|");
|
|
893
|
+
if (sep === -1) return { s, e: s };
|
|
894
|
+
const tail = raw.slice(sep + 1);
|
|
895
|
+
if (tail === "inf") return { s, e: Infinity };
|
|
896
|
+
const e = Number.parseInt(tail, 10);
|
|
897
|
+
return { s, e: Number.isFinite(e) ? e : s };
|
|
898
|
+
}
|
|
899
|
+
function mergeTagState(map, tag, next) {
|
|
900
|
+
const prev = map.get(tag);
|
|
901
|
+
if (!prev || next.s > prev.s) map.set(tag, next);
|
|
902
|
+
}
|
|
886
903
|
function gateRedis(state) {
|
|
887
904
|
const fb = state.opts.fallback;
|
|
888
905
|
const ibp = state.opts.rest.isBuildPhase;
|
|
@@ -1067,21 +1084,17 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
1067
1084
|
const freshnessTags = softTags.length > 0 ? [...softTags, ...envelope.tags] : envelope.tags;
|
|
1068
1085
|
let tagStale = false;
|
|
1069
1086
|
if (freshnessTags.length > 0) {
|
|
1070
|
-
|
|
1087
|
+
const now = Date.now();
|
|
1071
1088
|
for (const t of freshnessTags) {
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
);
|
|
1076
|
-
if (
|
|
1077
|
-
}
|
|
1078
|
-
if (mostRecent > envelope.timestamp) {
|
|
1079
|
-
if (state.opts.staleWhileRevalidate && partition.freshness !== "expired") {
|
|
1080
|
-
tagStale = true;
|
|
1081
|
-
} else {
|
|
1089
|
+
const local = state.localTagTimestamps.get(t);
|
|
1090
|
+
const mem = state.memTagExp.get(t);
|
|
1091
|
+
const st = local && mem ? local.s >= mem.s ? local : mem : local ?? mem;
|
|
1092
|
+
if (!st || st.s <= envelope.timestamp) continue;
|
|
1093
|
+
if (now >= st.e || partition.freshness === "expired") {
|
|
1082
1094
|
state.emit({ type: "cache.miss", meta: { reason: "tag-invalidated" } });
|
|
1083
1095
|
return void 0;
|
|
1084
1096
|
}
|
|
1097
|
+
tagStale = true;
|
|
1085
1098
|
}
|
|
1086
1099
|
}
|
|
1087
1100
|
const freshness = tagStale ? "stale" : partition.freshness;
|
|
@@ -1108,15 +1121,13 @@ async function getImpl(state, cacheKey, softTags) {
|
|
|
1108
1121
|
meta: { freshness }
|
|
1109
1122
|
});
|
|
1110
1123
|
}
|
|
1111
|
-
const revalidateMs = Math.max(0, envelope.revalidate) * 1e3;
|
|
1112
|
-
const outTimestamp = tagStale ? Math.min(envelope.timestamp, Date.now() - revalidateMs - 1) : envelope.timestamp;
|
|
1113
1124
|
return {
|
|
1114
1125
|
value: bufferToStream(Buffer.from(envelope.value, "base64")),
|
|
1115
1126
|
tags: envelope.tags,
|
|
1116
1127
|
stale: envelope.stale,
|
|
1117
|
-
timestamp:
|
|
1128
|
+
timestamp: envelope.timestamp,
|
|
1118
1129
|
expire: envelope.expire,
|
|
1119
|
-
revalidate: envelope.revalidate
|
|
1130
|
+
revalidate: tagStale ? -1 : envelope.revalidate
|
|
1120
1131
|
};
|
|
1121
1132
|
}
|
|
1122
1133
|
async function setImpl(state, cacheKey, pendingEntry) {
|
|
@@ -1130,6 +1141,10 @@ async function setImpl(state, cacheKey, pendingEntry) {
|
|
|
1130
1141
|
state.emit({ type: "cache.set.failed", meta: { reason: "pending-rejected" } });
|
|
1131
1142
|
return;
|
|
1132
1143
|
}
|
|
1144
|
+
if (entry.expire === 0 && !process.env.__NEXT_DEV_SERVER) {
|
|
1145
|
+
state.emit({ type: "cache.set.skipped", meta: { reason: "dynamic-entry" } });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1133
1148
|
let buf;
|
|
1134
1149
|
try {
|
|
1135
1150
|
buf = await readStreamFully(entry.value);
|
|
@@ -1235,11 +1250,13 @@ async function ensureTagSubscription(state) {
|
|
|
1235
1250
|
try {
|
|
1236
1251
|
const parsed = JSON.parse(message);
|
|
1237
1252
|
if (!Array.isArray(parsed.t) || typeof parsed.ts !== "number") return;
|
|
1253
|
+
const incoming = {
|
|
1254
|
+
s: parsed.ts,
|
|
1255
|
+
e: typeof parsed.e === "number" ? parsed.e : Infinity
|
|
1256
|
+
};
|
|
1238
1257
|
for (const tag of parsed.t) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
const prevMem = state.memTagExp.get(tag) ?? 0;
|
|
1242
|
-
if (parsed.ts > prevMem) state.memTagExp.set(tag, parsed.ts);
|
|
1258
|
+
mergeTagState(state.localTagTimestamps, tag, incoming);
|
|
1259
|
+
mergeTagState(state.memTagExp, tag, incoming);
|
|
1243
1260
|
}
|
|
1244
1261
|
pruneOldestByTimestamp(state.localTagTimestamps, MAX_LOCAL_TAG_ENTRIES);
|
|
1245
1262
|
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
@@ -1291,17 +1308,17 @@ async function refreshTagsImpl(state) {
|
|
|
1291
1308
|
for (let i = 0; i < keys.length; i += 1) {
|
|
1292
1309
|
const v = values[i];
|
|
1293
1310
|
if (!v) continue;
|
|
1294
|
-
const
|
|
1295
|
-
if (!
|
|
1311
|
+
const st = decodeTagMarker(v);
|
|
1312
|
+
if (!st) continue;
|
|
1296
1313
|
const key = keys[i];
|
|
1297
1314
|
if (key === void 0) continue;
|
|
1298
1315
|
const tag = stripExpPrefix(state, key);
|
|
1299
|
-
if (tag) fresh.set(tag,
|
|
1316
|
+
if (tag) fresh.set(tag, st);
|
|
1300
1317
|
}
|
|
1301
1318
|
}
|
|
1302
|
-
for (const [tag,
|
|
1303
|
-
if (
|
|
1304
|
-
fresh.set(tag,
|
|
1319
|
+
for (const [tag, st] of state.localTagTimestamps) {
|
|
1320
|
+
if (st.s >= scanStartMs && (fresh.get(tag)?.s ?? 0) < st.s) {
|
|
1321
|
+
fresh.set(tag, st);
|
|
1305
1322
|
}
|
|
1306
1323
|
}
|
|
1307
1324
|
state.localTagTimestamps = fresh;
|
|
@@ -1319,7 +1336,7 @@ function escapeMatch(s) {
|
|
|
1319
1336
|
var MAX_LOCAL_TAG_ENTRIES = 1e4;
|
|
1320
1337
|
function pruneOldestByTimestamp(map, maxEntries) {
|
|
1321
1338
|
if (map.size <= maxEntries) return;
|
|
1322
|
-
const byAge = [...map.entries()].sort((a, b) => a[1] - b[1]);
|
|
1339
|
+
const byAge = [...map.entries()].sort((a, b) => a[1].s - b[1].s);
|
|
1323
1340
|
const drop = map.size - maxEntries;
|
|
1324
1341
|
for (let i = 0; i < drop; i += 1) {
|
|
1325
1342
|
const entry = byAge[i];
|
|
@@ -1336,9 +1353,9 @@ async function getExpirationImpl(state, tags) {
|
|
|
1336
1353
|
let max = 0;
|
|
1337
1354
|
for (const t of tags) {
|
|
1338
1355
|
const local = state.localTagTimestamps.get(t);
|
|
1339
|
-
if (local
|
|
1356
|
+
if (local && Number.isFinite(local.e) && local.e > max) max = local.e;
|
|
1340
1357
|
const mem = state.memTagExp.get(t);
|
|
1341
|
-
if (mem
|
|
1358
|
+
if (mem && Number.isFinite(mem.e) && mem.e > max) max = mem.e;
|
|
1342
1359
|
}
|
|
1343
1360
|
if (max > 0) {
|
|
1344
1361
|
state.emit({ type: "tag.expiration.read", meta: { source: "local", count: tags.length } });
|
|
@@ -1359,8 +1376,8 @@ async function getExpirationImpl(state, tags) {
|
|
|
1359
1376
|
let m = 0;
|
|
1360
1377
|
for (const v of values) {
|
|
1361
1378
|
if (!v) continue;
|
|
1362
|
-
const
|
|
1363
|
-
if (Number.isFinite(
|
|
1379
|
+
const st = decodeTagMarker(v);
|
|
1380
|
+
if (st && Number.isFinite(st.e) && st.e > m) m = st.e;
|
|
1364
1381
|
}
|
|
1365
1382
|
return m;
|
|
1366
1383
|
}
|
|
@@ -1371,11 +1388,14 @@ async function getExpirationImpl(state, tags) {
|
|
|
1371
1388
|
}
|
|
1372
1389
|
async function updateTagsImpl(state, tags, durations) {
|
|
1373
1390
|
if (tags.length === 0) return;
|
|
1374
|
-
const isHardExpire = durations
|
|
1391
|
+
const isHardExpire = durations === void 0 || durations.expire === 0;
|
|
1375
1392
|
const now = Date.now();
|
|
1393
|
+
const deadline = isHardExpire ? now : durations.expire !== void 0 && Number.isFinite(durations.expire) ? now + durations.expire * 1e3 : Infinity;
|
|
1394
|
+
const tagState = { s: now, e: deadline };
|
|
1395
|
+
const marker = encodeTagMarker(tagState);
|
|
1376
1396
|
for (const t of tags) {
|
|
1377
|
-
state.localTagTimestamps
|
|
1378
|
-
state.memTagExp
|
|
1397
|
+
mergeTagState(state.localTagTimestamps, t, tagState);
|
|
1398
|
+
mergeTagState(state.memTagExp, t, tagState);
|
|
1379
1399
|
}
|
|
1380
1400
|
pruneOldestByTimestamp(state.memTagExp, MAX_LOCAL_TAG_ENTRIES);
|
|
1381
1401
|
if (!gateRedis(state)) {
|
|
@@ -1413,8 +1433,8 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1413
1433
|
client,
|
|
1414
1434
|
"revalidateHard",
|
|
1415
1435
|
[tagKey(state, t), tagExpKey(state, t)],
|
|
1416
|
-
[
|
|
1417
|
-
) : client.set(tagExpKey(state, t),
|
|
1436
|
+
[marker, String(TAG_EXP_MARKER_TTL_SEC)]
|
|
1437
|
+
) : client.set(tagExpKey(state, t), marker, {
|
|
1418
1438
|
EX: TAG_EXP_MARKER_TTL_SEC
|
|
1419
1439
|
})
|
|
1420
1440
|
)
|
|
@@ -1430,7 +1450,9 @@ async function updateTagsImpl(state, tags, durations) {
|
|
|
1430
1450
|
const client = await state.conn.getOrConnect();
|
|
1431
1451
|
await client?.publish?.(
|
|
1432
1452
|
invalChannel(state),
|
|
1433
|
-
JSON.stringify(
|
|
1453
|
+
JSON.stringify(
|
|
1454
|
+
deadline === Infinity ? { t: tags, ts: now } : { t: tags, ts: now, e: deadline }
|
|
1455
|
+
)
|
|
1434
1456
|
);
|
|
1435
1457
|
} catch {
|
|
1436
1458
|
}
|
|
@@ -1472,11 +1494,12 @@ function isEnvelope(value) {
|
|
|
1472
1494
|
function isLegacyBufferShape(value) {
|
|
1473
1495
|
return typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data);
|
|
1474
1496
|
}
|
|
1475
|
-
function replacer(
|
|
1476
|
-
|
|
1497
|
+
function replacer(key, value) {
|
|
1498
|
+
const raw = this[key];
|
|
1499
|
+
if (Buffer.isBuffer(raw)) {
|
|
1477
1500
|
const env = {
|
|
1478
1501
|
__type: "Buffer",
|
|
1479
|
-
data:
|
|
1502
|
+
data: raw.toString("base64")
|
|
1480
1503
|
};
|
|
1481
1504
|
return env;
|
|
1482
1505
|
}
|