layercache 1.3.1 → 1.3.3

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/dist/cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- RedisTagIndex
4
- } from "./chunk-BQLL6IM5.js";
3
+ RedisTagIndex,
4
+ validateCacheKey,
5
+ validatePattern,
6
+ validateTag
7
+ } from "./chunk-BORDQ3LA.js";
5
8
  import {
6
9
  isStoredValueEnvelope,
7
10
  resolveStoredValue
@@ -46,13 +49,17 @@ async function main(argv = process.argv.slice(2)) {
46
49
  throw new Error(`Failed to connect to Redis at ${maskRedisUrl(redisUrl)}: ${message}`);
47
50
  });
48
51
  if (args.command === "stats") {
49
- const keys = await scanKeys(redis, args.pattern ?? "*");
50
- process.stdout.write(`${JSON.stringify({ totalKeys: keys.length, pattern: args.pattern ?? "*" }, null, 2)}
52
+ const pattern = args.pattern ?? "*";
53
+ if (args.pattern && !validateCliInput(args.pattern, validatePattern)) return;
54
+ const keys = await scanKeys(redis, pattern);
55
+ process.stdout.write(`${JSON.stringify({ totalKeys: keys.length, pattern }, null, 2)}
51
56
  `);
52
57
  return;
53
58
  }
54
59
  if (args.command === "keys") {
55
- const keys = await scanKeys(redis, args.pattern ?? "*");
60
+ const pattern = args.pattern ?? "*";
61
+ if (args.pattern && !validateCliInput(args.pattern, validatePattern)) return;
62
+ const keys = await scanKeys(redis, pattern);
56
63
  if (keys.length > 0) {
57
64
  process.stdout.write(`${keys.join("\n")}
58
65
  `);
@@ -61,6 +68,7 @@ async function main(argv = process.argv.slice(2)) {
61
68
  }
62
69
  if (args.command === "invalidate") {
63
70
  if (args.tag) {
71
+ if (!validateCliInput(args.tag, validateTag)) return;
64
72
  const tagIndex = new RedisTagIndex({ client: redis, prefix: args.tagIndexPrefix ?? "layercache:tag-index" });
65
73
  const keys2 = await tagIndex.keysForTag(args.tag);
66
74
  if (keys2.length > 0) {
@@ -70,11 +78,18 @@ async function main(argv = process.argv.slice(2)) {
70
78
  `);
71
79
  return;
72
80
  }
73
- const keys = await scanKeys(redis, args.pattern ?? "*");
81
+ const effectivePattern = args.pattern ?? "*";
82
+ if (args.pattern && !validateCliInput(args.pattern, validatePattern)) return;
83
+ const keys = await scanKeys(redis, effectivePattern);
84
+ if (!args.pattern && !args.force && keys.length > 0) {
85
+ process.stderr.write(`Warning: this operation will invalidate ${keys.length} keys. Use --force to confirm.
86
+ `);
87
+ return;
88
+ }
74
89
  if (keys.length > 0) {
75
90
  await batchDelete(redis, keys);
76
91
  }
77
- process.stdout.write(`${JSON.stringify({ deletedKeys: keys.length, pattern: args.pattern ?? "*" }, null, 2)}
92
+ process.stdout.write(`${JSON.stringify({ deletedKeys: keys.length, pattern: effectivePattern }, null, 2)}
78
93
  `);
79
94
  return;
80
95
  }
@@ -82,6 +97,7 @@ async function main(argv = process.argv.slice(2)) {
82
97
  if (!args.key) {
83
98
  throw new Error("inspect requires --key <key>.");
84
99
  }
100
+ if (!validateCliInput(args.key, validateCacheKey)) return;
85
101
  const payload = await redis.getBuffer(args.key);
86
102
  const ttl = await redis.ttl(args.key);
87
103
  const decoded = decodeInspectablePayload(payload);
@@ -156,6 +172,8 @@ function parseArgs(argv) {
156
172
  index += 1;
157
173
  } else if (token === "--require-tls") {
158
174
  parsed.requireTls = true;
175
+ } else if (token === "--force") {
176
+ parsed.force = true;
159
177
  }
160
178
  }
161
179
  return parsed;
@@ -232,6 +250,18 @@ function maskRedisUrl(url) {
232
250
  return url.replace(/:([^@/]+)@/, ":***@");
233
251
  }
234
252
  }
253
+ function validateCliInput(value, validator) {
254
+ try {
255
+ validator(value);
256
+ return true;
257
+ } catch (error) {
258
+ const message = error instanceof Error ? error.message : String(error);
259
+ process.stderr.write(`Error: ${message}
260
+ `);
261
+ process.exitCode = 1;
262
+ return false;
263
+ }
264
+ }
235
265
  if (process.argv[1]?.endsWith("cli.cjs") || process.argv[1]?.endsWith("cli.js")) {
236
266
  void main();
237
267
  }
package/dist/edge.cjs CHANGED
@@ -339,7 +339,7 @@ var MAX_PATTERN_RECURSION_DEPTH = 500;
339
339
  var TagIndex = class {
340
340
  tagToKeys = /* @__PURE__ */ new Map();
341
341
  keyToTags = /* @__PURE__ */ new Map();
342
- knownKeys = /* @__PURE__ */ new Set();
342
+ knownKeys = /* @__PURE__ */ new Map();
343
343
  maxKnownKeys;
344
344
  nextNodeId = 1;
345
345
  root = this.createTrieNode();
@@ -427,10 +427,11 @@ var TagIndex = class {
427
427
  };
428
428
  }
429
429
  insertKnownKey(key) {
430
- if (this.knownKeys.has(key)) {
430
+ const isNew = !this.knownKeys.has(key);
431
+ this.knownKeys.set(key, Date.now());
432
+ if (!isNew) {
431
433
  return;
432
434
  }
433
- this.knownKeys.add(key);
434
435
  let node = this.root;
435
436
  for (const character of key) {
436
437
  let child = node.children.get(character);
@@ -525,14 +526,13 @@ var TagIndex = class {
525
526
  if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
526
527
  return;
527
528
  }
529
+ const sorted = [...this.knownKeys.entries()].sort((a, b) => a[1] - b[1]);
528
530
  const toRemove = Math.ceil(this.maxKnownKeys * 0.1);
529
- let removed = 0;
530
- for (const key of this.knownKeys) {
531
- if (removed >= toRemove) {
532
- break;
531
+ for (let i = 0; i < toRemove && i < sorted.length; i += 1) {
532
+ const entry = sorted[i];
533
+ if (entry) {
534
+ this.removeKey(entry[0]);
533
535
  }
534
- this.removeKey(key);
535
- removed += 1;
536
536
  }
537
537
  }
538
538
  removeKey(key) {
package/dist/edge.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  MemoryLayer,
3
3
  TagIndex,
4
4
  createHonoCacheMiddleware
5
- } from "./chunk-GJBKCFE6.js";
5
+ } from "./chunk-5RCAX2BQ.js";
6
6
  import {
7
7
  PatternMatcher
8
8
  } from "./chunk-4PPBOOXT.js";
package/dist/index.cjs CHANGED
@@ -961,6 +961,7 @@ var CacheStackLayerWriter = class {
961
961
  };
962
962
 
963
963
  // src/internal/CacheStackMaintenance.ts
964
+ var MAX_KEY_EPOCHS = 5e4;
964
965
  var CacheStackMaintenance = class {
965
966
  keyEpochs = /* @__PURE__ */ new Map();
966
967
  writeBehindQueue = [];
@@ -1004,6 +1005,7 @@ var CacheStackMaintenance = class {
1004
1005
  for (const key of keys) {
1005
1006
  this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
1006
1007
  }
1008
+ this.pruneKeyEpochsIfNeeded();
1007
1009
  }
1008
1010
  isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
1009
1011
  if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
@@ -1056,6 +1058,16 @@ var CacheStackMaintenance = class {
1056
1058
  async waitForGenerationCleanup() {
1057
1059
  await this.generationCleanupPromise;
1058
1060
  }
1061
+ pruneKeyEpochsIfNeeded() {
1062
+ if (this.keyEpochs.size <= MAX_KEY_EPOCHS) {
1063
+ return;
1064
+ }
1065
+ const sorted = [...this.keyEpochs.entries()].sort((a, b) => a[1] - b[1]);
1066
+ const toDelete = Math.ceil(sorted.length * 0.1);
1067
+ for (let i = 0; i < toDelete; i++) {
1068
+ this.keyEpochs.delete(sorted[i][0]);
1069
+ }
1070
+ }
1059
1071
  };
1060
1072
 
1061
1073
  // src/internal/CacheStackRuntimePolicy.ts
@@ -1095,16 +1107,16 @@ function planFreshReadPolicies({
1095
1107
  }
1096
1108
 
1097
1109
  // src/internal/CacheStackSnapshotManager.ts
1098
- var import_node_crypto = require("crypto");
1099
1110
  var import_node_fs = require("fs");
1100
- var import_node_path = __toESM(require("path"), 1);
1101
1111
 
1102
1112
  // src/internal/CacheSnapshotFile.ts
1103
- function isWithinSnapshotBase(realBaseDir, candidatePath, pathSeparator, path2) {
1104
- const relative = path2.relative(realBaseDir, candidatePath);
1105
- return !(relative === ".." || relative.startsWith(`..${pathSeparator}`) || path2.isAbsolute(relative));
1113
+ var import_node_crypto = require("crypto");
1114
+ var import_promises = require("fs/promises");
1115
+ function isWithinSnapshotBase(realBaseDir, candidatePath, pathSeparator, path) {
1116
+ const relative = path.relative(realBaseDir, candidatePath);
1117
+ return !(relative === ".." || relative.startsWith(`..${pathSeparator}`) || path.isAbsolute(relative));
1106
1118
  }
1107
- async function findExistingAncestor(directory, fs3, path2) {
1119
+ async function findExistingAncestor(directory, fs3, path) {
1108
1120
  let current = directory;
1109
1121
  while (true) {
1110
1122
  try {
@@ -1115,7 +1127,7 @@ async function findExistingAncestor(directory, fs3, path2) {
1115
1127
  throw error;
1116
1128
  }
1117
1129
  }
1118
- const parent = path2.dirname(current);
1130
+ const parent = path.dirname(current);
1119
1131
  if (parent === current) {
1120
1132
  return current;
1121
1133
  }
@@ -1130,36 +1142,36 @@ async function validateSnapshotFilePath(filePath, mode, snapshotBaseDir, cwd = p
1130
1142
  throw new Error("filePath must not contain null bytes.");
1131
1143
  }
1132
1144
  const { promises: fs3 } = await import("fs");
1133
- const path2 = await import("path");
1134
- const resolved = path2.resolve(filePath);
1135
- const baseDir = snapshotBaseDir === false ? false : path2.resolve(snapshotBaseDir ?? cwd);
1145
+ const path = await import("path");
1146
+ const resolved = path.resolve(filePath);
1147
+ const baseDir = snapshotBaseDir === false ? false : path.resolve(snapshotBaseDir ?? cwd);
1136
1148
  if (baseDir === false) {
1137
1149
  return resolved;
1138
1150
  }
1139
1151
  await fs3.mkdir(baseDir, { recursive: true });
1140
1152
  const realBaseDir = await fs3.realpath(baseDir);
1141
- if (!isWithinSnapshotBase(realBaseDir, resolved, path2.sep, path2)) {
1153
+ if (!isWithinSnapshotBase(realBaseDir, resolved, path.sep, path)) {
1142
1154
  throw new Error(`filePath is outside the allowed snapshot directory: ${realBaseDir}`);
1143
1155
  }
1144
1156
  if (mode === "read") {
1145
1157
  const realTarget = await fs3.realpath(resolved);
1146
- if (!isWithinSnapshotBase(realBaseDir, realTarget, path2.sep, path2)) {
1158
+ if (!isWithinSnapshotBase(realBaseDir, realTarget, path.sep, path)) {
1147
1159
  throw new Error(`filePath is outside the allowed snapshot directory: ${realBaseDir}`);
1148
1160
  }
1149
1161
  return realTarget;
1150
1162
  }
1151
- const parentDir = path2.dirname(resolved);
1152
- const existingAncestor = await findExistingAncestor(parentDir, fs3, path2);
1163
+ const parentDir = path.dirname(resolved);
1164
+ const existingAncestor = await findExistingAncestor(parentDir, fs3, path);
1153
1165
  const realExistingAncestor = await fs3.realpath(existingAncestor);
1154
- if (!isWithinSnapshotBase(realBaseDir, realExistingAncestor, path2.sep, path2)) {
1166
+ if (!isWithinSnapshotBase(realBaseDir, realExistingAncestor, path.sep, path)) {
1155
1167
  throw new Error(`filePath is outside the allowed snapshot directory: ${realBaseDir}`);
1156
1168
  }
1157
1169
  await fs3.mkdir(parentDir, { recursive: true });
1158
1170
  const realParentDir = await fs3.realpath(parentDir);
1159
- if (!isWithinSnapshotBase(realBaseDir, realParentDir, path2.sep, path2)) {
1171
+ if (!isWithinSnapshotBase(realBaseDir, realParentDir, path.sep, path)) {
1160
1172
  throw new Error(`filePath is outside the allowed snapshot directory: ${realBaseDir}`);
1161
1173
  }
1162
- const targetPath = path2.join(realParentDir, path2.basename(resolved));
1174
+ const targetPath = path.join(realParentDir, path.basename(resolved));
1163
1175
  try {
1164
1176
  const existing = await fs3.lstat(targetPath);
1165
1177
  if (existing.isSymbolicLink()) {
@@ -1194,6 +1206,17 @@ async function readUtf8HandleWithLimit(handle, byteLimit) {
1194
1206
  }
1195
1207
  return Buffer.concat(chunks).toString("utf8");
1196
1208
  }
1209
+ function atomicWriteTempPath(targetPath) {
1210
+ return `${targetPath}.tmp-${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
1211
+ }
1212
+ async function commitAtomicWrite(tempPath, targetPath) {
1213
+ try {
1214
+ await (0, import_promises.rename)(tempPath, targetPath);
1215
+ } catch (error) {
1216
+ await (0, import_promises.unlink)(tempPath).catch(() => void 0);
1217
+ throw error;
1218
+ }
1219
+ }
1197
1220
 
1198
1221
  // src/internal/StructuredDataSanitizer.ts
1199
1222
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
@@ -1272,10 +1295,7 @@ var CacheStackSnapshotManager = class {
1272
1295
  }
1273
1296
  async persistToFile(filePath, snapshotBaseDir, maxEntries) {
1274
1297
  const targetPath = await validateSnapshotFilePath(filePath, "write", snapshotBaseDir);
1275
- const tempPath = import_node_path.default.join(
1276
- import_node_path.default.dirname(targetPath),
1277
- `.layercache-${process.pid}-${Date.now()}-${(0, import_node_crypto.randomBytes)(8).toString("hex")}.tmp`
1278
- );
1298
+ const tempPath = atomicWriteTempPath(targetPath);
1279
1299
  let handle;
1280
1300
  try {
1281
1301
  handle = await import_node_fs.promises.open(tempPath, "wx");
@@ -1290,7 +1310,7 @@ var CacheStackSnapshotManager = class {
1290
1310
  await openedHandle.writeFile(wroteAny ? "\n]" : "]", "utf8");
1291
1311
  await openedHandle.close();
1292
1312
  handle = void 0;
1293
- await import_node_fs.promises.rename(tempPath, targetPath);
1313
+ await commitAtomicWrite(tempPath, targetPath);
1294
1314
  } catch (error) {
1295
1315
  await handle?.close().catch(() => void 0);
1296
1316
  await import_node_fs.promises.unlink(tempPath).catch(() => void 0);
@@ -1605,6 +1625,7 @@ var CircuitBreakerManager = class {
1605
1625
 
1606
1626
  // src/internal/FetchRateLimiter.ts
1607
1627
  var MAX_BUCKETS = 1e4;
1628
+ var MAX_QUEUE_PER_BUCKET = 1e4;
1608
1629
  var FetchRateLimiter = class {
1609
1630
  buckets = /* @__PURE__ */ new Map();
1610
1631
  queuesByBucket = /* @__PURE__ */ new Map();
@@ -1613,6 +1634,7 @@ var FetchRateLimiter = class {
1613
1634
  nextFetcherBucketId = 0;
1614
1635
  drainTimer;
1615
1636
  isDisposed = false;
1637
+ rateLimitBypasses = 0;
1616
1638
  async schedule(options, context, task) {
1617
1639
  if (this.isDisposed) {
1618
1640
  throw new Error("FetchRateLimiter has been disposed.");
@@ -1627,6 +1649,11 @@ var FetchRateLimiter = class {
1627
1649
  return new Promise((resolve2, reject) => {
1628
1650
  const bucketKey = this.resolveBucketKey(normalized, context);
1629
1651
  const queue = this.queuesByBucket.get(bucketKey) ?? [];
1652
+ if (queue.length >= MAX_QUEUE_PER_BUCKET) {
1653
+ this.rateLimitBypasses += 1;
1654
+ task().then(resolve2, reject);
1655
+ return;
1656
+ }
1630
1657
  queue.push({
1631
1658
  bucketKey,
1632
1659
  options: normalized,
@@ -1931,7 +1958,13 @@ var MetricsCollector = class {
1931
1958
  };
1932
1959
 
1933
1960
  // src/internal/TtlResolver.ts
1961
+ var import_node_crypto2 = require("crypto");
1934
1962
  var DEFAULT_NEGATIVE_TTL_SECONDS = 60;
1963
+ var secureRandom = {
1964
+ value() {
1965
+ return (0, import_node_crypto2.randomBytes)(4).readUInt32BE(0) / 4294967296;
1966
+ }
1967
+ };
1935
1968
  var TtlResolver = class {
1936
1969
  accessProfiles = /* @__PURE__ */ new Map();
1937
1970
  maxProfileEntries;
@@ -1994,7 +2027,7 @@ var TtlResolver = class {
1994
2027
  if (!ttl || ttl <= 0 || !jitter || jitter <= 0) {
1995
2028
  return ttl;
1996
2029
  }
1997
- const delta = (Math.random() * 2 - 1) * jitter;
2030
+ const delta = (secureRandom.value() * 2 - 1) * jitter;
1998
2031
  return Math.max(1, Math.round(ttl + delta));
1999
2032
  }
2000
2033
  resolvePolicyTtl(key, value, policy) {
@@ -2046,7 +2079,7 @@ var MAX_PATTERN_RECURSION_DEPTH = 500;
2046
2079
  var TagIndex = class {
2047
2080
  tagToKeys = /* @__PURE__ */ new Map();
2048
2081
  keyToTags = /* @__PURE__ */ new Map();
2049
- knownKeys = /* @__PURE__ */ new Set();
2082
+ knownKeys = /* @__PURE__ */ new Map();
2050
2083
  maxKnownKeys;
2051
2084
  nextNodeId = 1;
2052
2085
  root = this.createTrieNode();
@@ -2134,10 +2167,11 @@ var TagIndex = class {
2134
2167
  };
2135
2168
  }
2136
2169
  insertKnownKey(key) {
2137
- if (this.knownKeys.has(key)) {
2170
+ const isNew = !this.knownKeys.has(key);
2171
+ this.knownKeys.set(key, Date.now());
2172
+ if (!isNew) {
2138
2173
  return;
2139
2174
  }
2140
- this.knownKeys.add(key);
2141
2175
  let node = this.root;
2142
2176
  for (const character of key) {
2143
2177
  let child = node.children.get(character);
@@ -2232,14 +2266,13 @@ var TagIndex = class {
2232
2266
  if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
2233
2267
  return;
2234
2268
  }
2269
+ const sorted = [...this.knownKeys.entries()].sort((a, b) => a[1] - b[1]);
2235
2270
  const toRemove = Math.ceil(this.maxKnownKeys * 0.1);
2236
- let removed = 0;
2237
- for (const key of this.knownKeys) {
2238
- if (removed >= toRemove) {
2239
- break;
2271
+ for (let i = 0; i < toRemove && i < sorted.length; i += 1) {
2272
+ const entry = sorted[i];
2273
+ if (entry) {
2274
+ this.removeKey(entry[0]);
2240
2275
  }
2241
- this.removeKey(key);
2242
- removed += 1;
2243
2276
  }
2244
2277
  }
2245
2278
  removeKey(key) {
@@ -2264,19 +2297,19 @@ var TagIndex = class {
2264
2297
  if (!this.knownKeys.delete(key)) {
2265
2298
  return;
2266
2299
  }
2267
- const path2 = [];
2300
+ const path = [];
2268
2301
  let node = this.root;
2269
2302
  for (const character of key) {
2270
2303
  const child = node.children.get(character);
2271
2304
  if (!child) {
2272
2305
  return;
2273
2306
  }
2274
- path2.push([node, character]);
2307
+ path.push([node, character]);
2275
2308
  node = child;
2276
2309
  }
2277
2310
  node.terminal = false;
2278
- for (let index = path2.length - 1; index >= 0; index -= 1) {
2279
- const entry = path2[index];
2311
+ for (let index = path.length - 1; index >= 0; index -= 1) {
2312
+ const entry = path[index];
2280
2313
  if (!entry) {
2281
2314
  continue;
2282
2315
  }
@@ -3359,7 +3392,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
3359
3392
  } catch (error) {
3360
3393
  if (this.backgroundRefreshAbort.get(key)) return;
3361
3394
  this.metricsCollector.increment("refreshErrors");
3362
- this.logger.debug?.("refresh-error", { key, error: this.formatError(error) });
3395
+ this.logger.warn?.("background-refresh-error", { key, error: this.formatError(error) });
3363
3396
  } finally {
3364
3397
  this.backgroundRefreshes.delete(key);
3365
3398
  this.backgroundRefreshAbort.delete(key);
@@ -3657,7 +3690,12 @@ var CacheStack = class extends import_node_events.EventEmitter {
3657
3690
  }
3658
3691
  }
3659
3692
  shouldSkipLayer(layer) {
3660
- return shouldSkipLayer(this.layerDegradedUntil.get(layer.name));
3693
+ const degradedUntil = this.layerDegradedUntil.get(layer.name);
3694
+ const skip = shouldSkipLayer(degradedUntil);
3695
+ if (!skip && degradedUntil !== void 0) {
3696
+ this.layerDegradedUntil.delete(layer.name);
3697
+ }
3698
+ return skip;
3661
3699
  }
3662
3700
  async handleLayerFailure(layer, operation, error) {
3663
3701
  const recovery = resolveRecoverableLayerFailure(this.options.gracefulDegradation);
@@ -4872,12 +4910,12 @@ var RedisLayer = class {
4872
4910
  };
4873
4911
 
4874
4912
  // src/layers/DiskLayer.ts
4875
- var import_node_crypto3 = require("crypto");
4913
+ var import_node_crypto4 = require("crypto");
4876
4914
  var import_node_fs2 = require("fs");
4877
- var import_node_path2 = require("path");
4915
+ var import_node_path = require("path");
4878
4916
 
4879
4917
  // src/internal/PayloadProtection.ts
4880
- var import_node_crypto2 = require("crypto");
4918
+ var import_node_crypto3 = require("crypto");
4881
4919
  var MAGIC_ENCRYPTED = Buffer.from("LCP1:");
4882
4920
  var MAGIC_SIGNED = Buffer.from("LCS1:");
4883
4921
  var ALGORITHM = "aes-256-gcm";
@@ -4890,11 +4928,11 @@ var PayloadProtection = class {
4890
4928
  constructor(options) {
4891
4929
  if (options.encryptionKey) {
4892
4930
  const raw = Buffer.isBuffer(options.encryptionKey) ? options.encryptionKey : Buffer.from(options.encryptionKey, "utf8");
4893
- this.encryptionKey = (0, import_node_crypto2.createHash)("sha256").update(raw).digest();
4931
+ this.encryptionKey = (0, import_node_crypto3.createHash)("sha256").update(raw).digest();
4894
4932
  }
4895
4933
  if (options.signingKey && !options.encryptionKey) {
4896
4934
  const raw = Buffer.isBuffer(options.signingKey) ? options.signingKey : Buffer.from(options.signingKey, "utf8");
4897
- this.signingKey = (0, import_node_crypto2.createHash)("sha256").update(raw).digest();
4935
+ this.signingKey = (0, import_node_crypto3.createHash)("sha256").update(raw).digest();
4898
4936
  }
4899
4937
  }
4900
4938
  /** Returns `true` when any protection (encryption or signing) is configured. */
@@ -4941,8 +4979,8 @@ var PayloadProtection = class {
4941
4979
  }
4942
4980
  // ── Encryption (AES-256-GCM) ──────────────────────────────────────────
4943
4981
  encrypt(plaintext, key) {
4944
- const iv = (0, import_node_crypto2.randomBytes)(IV_LENGTH);
4945
- const cipher = (0, import_node_crypto2.createCipheriv)(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
4982
+ const iv = (0, import_node_crypto3.randomBytes)(IV_LENGTH);
4983
+ const cipher = (0, import_node_crypto3.createCipheriv)(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
4946
4984
  const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
4947
4985
  const authTag = cipher.getAuthTag();
4948
4986
  return Buffer.concat([MAGIC_ENCRYPTED, iv, authTag, encrypted]);
@@ -4953,7 +4991,7 @@ var PayloadProtection = class {
4953
4991
  const authTag = payload.subarray(headerEnd + IV_LENGTH, headerEnd + IV_LENGTH + AUTH_TAG_LENGTH);
4954
4992
  const ciphertext = payload.subarray(headerEnd + IV_LENGTH + AUTH_TAG_LENGTH);
4955
4993
  try {
4956
- const decipher = (0, import_node_crypto2.createDecipheriv)(ALGORITHM, key, iv, {
4994
+ const decipher = (0, import_node_crypto3.createDecipheriv)(ALGORITHM, key, iv, {
4957
4995
  authTagLength: AUTH_TAG_LENGTH
4958
4996
  });
4959
4997
  decipher.setAuthTag(authTag);
@@ -4966,15 +5004,15 @@ var PayloadProtection = class {
4966
5004
  }
4967
5005
  // ── Signing (HMAC-SHA256) ─────────────────────────────────────────────
4968
5006
  sign(payload, key) {
4969
- const hmac = (0, import_node_crypto2.createHmac)("sha256", key).update(payload).digest();
5007
+ const hmac = (0, import_node_crypto3.createHmac)("sha256", key).update(payload).digest();
4970
5008
  return Buffer.concat([MAGIC_SIGNED, hmac, payload]);
4971
5009
  }
4972
5010
  verify(payload, key) {
4973
5011
  const headerEnd = MAGIC_SIGNED.length;
4974
5012
  const receivedHmac = payload.subarray(headerEnd, headerEnd + HMAC_LENGTH);
4975
5013
  const data = payload.subarray(headerEnd + HMAC_LENGTH);
4976
- const expectedHmac = (0, import_node_crypto2.createHmac)("sha256", key).update(data).digest();
4977
- if (receivedHmac.length !== HMAC_LENGTH || !(0, import_node_crypto2.timingSafeEqual)(receivedHmac, expectedHmac)) {
5014
+ const expectedHmac = (0, import_node_crypto3.createHmac)("sha256", key).update(data).digest();
5015
+ if (receivedHmac.length !== HMAC_LENGTH || !(0, import_node_crypto3.timingSafeEqual)(receivedHmac, expectedHmac)) {
4978
5016
  throw new PayloadProtectionError(
4979
5017
  "HMAC verification failed. The data may have been tampered with or the signingKey is incorrect."
4980
5018
  );
@@ -5054,7 +5092,7 @@ var DiskLayer = class {
5054
5092
  const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "utf8");
5055
5093
  const protectedPayload = this.protection.protect(raw);
5056
5094
  const targetPath = this.keyToPath(key);
5057
- const tempPath = `${targetPath}.${process.pid}.${Date.now()}.${(0, import_node_crypto3.randomBytes)(8).toString("hex")}.tmp`;
5095
+ const tempPath = `${targetPath}.${process.pid}.${Date.now()}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`;
5058
5096
  try {
5059
5097
  await import_node_fs2.promises.writeFile(tempPath, protectedPayload);
5060
5098
  await import_node_fs2.promises.rename(tempPath, targetPath);
@@ -5116,7 +5154,7 @@ var DiskLayer = class {
5116
5154
  return;
5117
5155
  }
5118
5156
  await this.deletePathsWithConcurrency(
5119
- entries.filter((name) => name.endsWith(".lc")).map((name) => (0, import_node_path2.join)(this.directory, name))
5157
+ entries.filter((name) => name.endsWith(".lc")).map((name) => (0, import_node_path.join)(this.directory, name))
5120
5158
  );
5121
5159
  });
5122
5160
  }
@@ -5154,8 +5192,8 @@ var DiskLayer = class {
5154
5192
  async dispose() {
5155
5193
  }
5156
5194
  keyToPath(key) {
5157
- const hash = (0, import_node_crypto3.createHash)("sha256").update(key).digest("hex");
5158
- return (0, import_node_path2.join)(this.directory, `${hash}.lc`);
5195
+ const hash = (0, import_node_crypto4.createHash)("sha256").update(key).digest("hex");
5196
+ return (0, import_node_path.join)(this.directory, `${hash}.lc`);
5159
5197
  }
5160
5198
  resolveDirectory(directory) {
5161
5199
  if (typeof directory !== "string" || directory.trim().length === 0) {
@@ -5164,7 +5202,7 @@ var DiskLayer = class {
5164
5202
  if (directory.includes("\0")) {
5165
5203
  throw new Error("DiskLayer.directory must not contain null bytes.");
5166
5204
  }
5167
- return (0, import_node_path2.resolve)(directory);
5205
+ return (0, import_node_path.resolve)(directory);
5168
5206
  }
5169
5207
  normalizeMaxFiles(maxFiles) {
5170
5208
  if (maxFiles === void 0) {
@@ -5247,7 +5285,7 @@ var DiskLayer = class {
5247
5285
  if (name === void 0) {
5248
5286
  return;
5249
5287
  }
5250
- const filePath = (0, import_node_path2.join)(this.directory, name);
5288
+ const filePath = (0, import_node_path.join)(this.directory, name);
5251
5289
  const raw = await this.readEntryFile(filePath);
5252
5290
  if (raw === null) {
5253
5291
  continue;
@@ -5323,7 +5361,7 @@ var DiskLayer = class {
5323
5361
  }
5324
5362
  const withStats = await Promise.all(
5325
5363
  lcFiles.map(async (name) => {
5326
- const filePath = (0, import_node_path2.join)(this.directory, name);
5364
+ const filePath = (0, import_node_path.join)(this.directory, name);
5327
5365
  try {
5328
5366
  const stat = await import_node_fs2.promises.stat(filePath);
5329
5367
  return { filePath, mtimeMs: stat.mtimeMs };
@@ -5439,7 +5477,7 @@ var MsgpackSerializer = class {
5439
5477
  };
5440
5478
 
5441
5479
  // src/singleflight/RedisSingleFlightCoordinator.ts
5442
- var import_node_crypto4 = require("crypto");
5480
+ var import_node_crypto5 = require("crypto");
5443
5481
  var RELEASE_SCRIPT = `
5444
5482
  if redis.call("get", KEYS[1]) == ARGV[1] then
5445
5483
  return redis.call("del", KEYS[1])
@@ -5463,7 +5501,7 @@ var RedisSingleFlightCoordinator = class {
5463
5501
  }
5464
5502
  async execute(key, options, worker, waiter) {
5465
5503
  const lockKey = `${this.prefix}:${encodeURIComponent(key)}`;
5466
- const token = (0, import_node_crypto4.randomUUID)();
5504
+ const token = (0, import_node_crypto5.randomUUID)();
5467
5505
  const acquired = await this.runCommand(
5468
5506
  `acquire("${key}")`,
5469
5507
  () => this.client.set(lockKey, token, "PX", options.leaseMs, "NX")