{"id":"e4e61100aec4b8f8","repo":"redis/node-redis","slug":"all-statistics-values-must-be-non-negative","errorCode":null,"errorMessage":"All statistics values must be non-negative","messagePattern":"All statistics values must be non-negative","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/client/cache.ts","lineNumber":51,"sourceCode":"   * Creates a new CacheStats instance with the specified statistics.\n   */\n  private constructor(\n    public readonly hitCount: number,\n    public readonly missCount: number,\n    public readonly loadSuccessCount: number,\n    public readonly loadFailureCount: number,\n    public readonly totalLoadTime: number,\n    public readonly evictionCount: number\n  ) {\n    if (\n      hitCount < 0 ||\n      missCount < 0 ||\n      loadSuccessCount < 0 ||\n      loadFailureCount < 0 ||\n      totalLoadTime < 0 ||\n      evictionCount < 0\n    ) {\n      throw new Error('All statistics values must be non-negative');\n    }\n  }\n\n  /**\n   * Creates a new CacheStats instance with the specified statistics.\n   *\n   * @param hitCount - Number of cache hits\n   * @param missCount - Number of cache misses\n   * @param loadSuccessCount - Number of successful cache loads\n   * @param loadFailureCount - Number of failed cache loads\n   * @param totalLoadTime - Total load time in milliseconds\n   * @param evictionCount - Number of cache evictions\n   */\n  static of(\n    hitCount = 0,\n    missCount = 0,\n    loadSuccessCount = 0,\n    loadFailureCount = 0,","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/redis/node-redis/blob/bb5beb56578573910e2ee8f39681edc214c41398/packages/client/lib/client/cache.ts#L33-L69","documentation":"CacheStats' constructor (reached via CacheStats.of) enforces that all six counters (hitCount, missCount, loadSuccessCount, loadFailureCount, totalLoadTime, evictionCount) are >= 0. A negative value would corrupt every derived rate (hitRate, missRate, loadFailureRate) and break the Caffeine-style stats contract, so the constructor rejects it outright.","triggerScenarios":"Calling CacheStats.of(...) with a negative argument directly; a StatsCounter.recordX(-n) producing a negative running total whose snapshot is then built; an arithmetic underflow in a custom StatsCounter; subtraction (minus) already clamps with Math.max(0,...), so hitting this implies a non-clamped code path.","commonSituations":"A custom StatsCounter that doesn't guard against negative inputs; concurrent decrement logic; test fixtures passing negative numbers; a bug where recordLoadFailure is called with a negative elapsed time.","solutions":["Ensure all record*(value) calls receive non-negative values (clamp with Math.max(0, value)).","If authoring a custom StatsCounter, guard recordHits/recordMisses/recordEvictions to reject negative counts and recordLoadSuccess/recordLoadFailure to reject negative times.","Audit the call sites that construct CacheStats directly (most users should only read snapshots)."],"exampleFix":"// before\ncounter.recordLoadSuccess(negativeElapsed);\n\n// after\ncounter.recordLoadSuccess(Math.max(0, negativeElapsed));","handlingStrategy":"validation","validationCode":"function safeRecord(counter, fn, value) {\n  if (typeof value === 'number' && value >= 0) fn.call(counter, value);\n}","typeGuard":"function isNonNegativeNumber(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0;\n}","tryCatchPattern":null,"preventionTips":["Clamp all counter inputs with Math.max(0, value).","Guard custom StatsCounter record* methods against negative arguments.","Avoid constructing CacheStats directly; read snapshots via the counter."],"tags":["cache","validation","statistics"],"analyzedSha":"bb5beb56578573910e2ee8f39681edc214c41398","analyzedAt":"2026-08-03T19:09:15.686Z","schemaVersion":2}