{"record":{"id":"f7c0b2a8c5289a66","repo":"santifer/career-ops","slug":"ratepermin-must-be-a-finite-number-0-got-rate","errorCode":null,"errorMessage":"ratePerMin must be a finite number > 0, got ${ratePerMin}","messagePattern":"ratePerMin must be a finite number > 0, got (.+?)","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"providers/_dns-cache.mjs","lineNumber":136,"sourceCode":" * Clock and timer are injectable so the tests are deterministic and offline.\n *\n * @param {object} [options] - Bucket tuning.\n * @param {number} [options.ratePerMin] - Sustained calls per minute. Must be > 0.\n * @param {number} [options.capacity] - Burst size, in tokens.\n * @param {() => number} [options.now] - Clock source, injectable for tests.\n * @param {(fn: Function, ms: number) => void} [options.setTimer] - Timer, injectable for tests.\n * @returns {{ take: (fn: Function) => void, pending: number, stats: () => { delayed: number, waitedMs: number } }}\n */\nexport function createTokenBucket(options = {}) {\n  const ratePerMin = options.ratePerMin ?? DEFAULT_LOOKUPS_PER_MIN;\n  const capacity = options.capacity ?? DEFAULT_BURST;\n  const now = options.now ?? Date.now;\n  const setTimer = options.setTimer ?? setTimeout;\n\n  // Caller-supplied, so validate rather than assert: a zero or negative rate\n  // would make the refill interval Infinity and hang every queued lookup.\n  if (!Number.isFinite(ratePerMin) || ratePerMin <= 0) {\n    throw new RangeError(`ratePerMin must be a finite number > 0, got ${ratePerMin}`);\n  }\n  if (!Number.isFinite(capacity) || capacity < 1) {\n    throw new RangeError(`capacity must be a finite number >= 1, got ${capacity}`);\n  }\n\n  const tokensPerMs = ratePerMin / 60_000;\n  let tokens = capacity;\n  let lastRefill = now();\n  /** @type {{ fn: Function, queuedAt: number }[]} */\n  const queue = [];\n  let timerPending = false;\n  let delayed = 0;\n  let waitedMs = 0;\n\n  function refill() {\n    const t = now();\n    tokens = Math.min(capacity, tokens + (t - lastRefill) * tokensPerMs);\n    lastRefill = t;","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/_dns-cache.mjs#L118-L154","documentation":"createTokenBucket() in providers/_dns-cache.mjs validates its ratePerMin argument with a RangeError. A zero, negative, NaN, or Infinity rate would make the token-refill interval Infinity, silently stalling every queued DNS lookup, so the factory rejects it up front rather than hanging at runtime.","triggerScenarios":"Calling createTokenBucket(ratePerMin, ...) with 0, a negative number, NaN (e.g. from parseFloat of a malformed env var), or Infinity — typically when the rate is computed from config/env like RATE_PER_MIN that is unset or unparsable.","commonSituations":"Missing or typo'd environment variable parsed with Number()/parseFloat returning NaN; a division producing NaN (e.g. requests/seconds where seconds is 0); hardcoded 0 to 'disable' the limiter (not supported — use Infinity capacity or bypass instead); copy-pasted config where the rate was left blank.","solutions":["Fix the rate source so it yields a finite positive number, e.g. const rate = Number(process.env.DNS_RATE_PER_MIN); if (!Number.isFinite(rate) || rate <= 0) rate = 60; (sane default).","Check the config/env value feeding the argument for typos, blank strings, or 0 meant as 'unlimited'.","If you truly want no limiting, pass a very large finite rate (or a huge capacity) rather than 0.","Wrap the division that computes the rate and guard the denominator before calling createTokenBucket."],"exampleFix":"// before\nconst bucket = createTokenBucket(Number(process.env.DNS_RATE), 10); // NaN if unset\n// after\nconst rate = Number(process.env.DNS_RATE ?? 60);\nif (!Number.isFinite(rate) || rate <= 0) throw new Error('DNS_RATE must be a positive number');\nconst bucket = createTokenBucket(rate, 10);","handlingStrategy":"validation","validationCode":"function toPositiveRate(raw, fallback = 60) {\n  const n = Number(raw);\n  return Number.isFinite(n) && n > 0 ? n : fallback;\n}\nconst bucket = createTokenBucket(toPositiveRate(process.env.DNS_RATE_PER_MIN), 10);","typeGuard":"function isPositiveFinite(n) {\n  return typeof n === 'number' && Number.isFinite(n) && n > 0;\n}","tryCatchPattern":"try {\n  bucket = createTokenBucket(ratePerMin, capacity);\n} catch (err) {\n  if (err instanceof RangeError && err.message.includes('ratePerMin')) {\n    console.warn(`Bad rate ${ratePerMin}, falling back to 60/min`);\n    bucket = createTokenBucket(60, capacity);\n  } else throw err;\n}","preventionTips":["Default every rate env var explicitly and validate with Number.isFinite + > 0 at read time.","Remember 0 does not mean 'unlimited' — use a large finite rate if you want effectively no limiting.","Guard divisions that compute the rate (denominator can be 0 -> NaN).","Unit-test bucket construction with edge inputs (0, NaN, Infinity, negative)."],"tags":["configuration","validation","rate-limiting","range-error"],"backgroundTag":"invalid-argument-range","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}