denoland/deno · error · TypeError

expireIn must be a non-negative integer: received

Error message

expireIn must be a non-negative integer: received ${expireIn}

What it means

`kv.set(key, value, { expireIn })` takes a relative TTL in whole milliseconds. `validateExpireIn` rejects anything that is not a non-negative integer — NaN, Infinity, fractional, and negative values — because such input reaches the native layer, overflows the absolute-expiry computation, and would panic the whole process (per the comment in the source).

Solutions

  1. Round and clamp: `Math.max(0, Math.round(expireIn))`
  2. Validate `Number.isInteger(expireIn) && expireIn >= 0` before the write
  3. Omit the option entirely when no expiration is intended (undefined is accepted)

Example fix

// before
await kv.set("session", data, { expireIn: ttlSeconds * 1.5 }); // fractional -> throws

// after
await kv.set("session", data, { expireIn: Math.max(0, Math.round(ttlSeconds * 1000)) });
Defensive patterns

Strategy: validation

Validate before calling

if (expireIn !== undefined && (!Number.isInteger(expireIn) || expireIn < 0)) {
  throw new Error(`invalid expireIn: ${expireIn}`);
}
await kv.set(key, value, { expireIn });

Type guard

const isValidExpireIn = (v: unknown): v is number | undefined =>
  v === undefined || (typeof v === "number" && Number.isInteger(v) && v >= 0);

Prevention

When it happens

Trigger: `expireIn: 1.5` (fractional ms), `expireIn: -1000`, `expireIn: Infinity`, or NaN from failed parsing; second-to-ms conversions producing floats (`ttlSeconds * 1000.0` on fractional input).

Common situations: TTLs derived from user input or config parsing; mixing seconds and milliseconds; math on undefined values passed straight into `kv.set`.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/e9123eb251c2b6d4. Report an issue: GitHub.

Appendix: source

Thrown at ext/kv/01_db.ts:86

    throw new TypeError(`Delay must be >= 0: received ${delay}`);
  }
  if (delay > maxQueueDelay) {
    throw new TypeError(
      `Delay cannot be greater than 30 days: received ${delay}`,
    );
  }
  if (NumberIsNaN(delay)) {
    throw new TypeError("Delay cannot be NaN");
  }
}

function validateExpireIn(expireIn: number | undefined) {
  if (expireIn === undefined) return;
  // Reject NaN, Infinity, fractional and negative values. A non-finite
  // expireIn otherwise reaches the native layer and overflows when computing
  // the absolute expiry, panicking the process.
  if (!NumberIsInteger(expireIn) || expireIn < 0) {
    throw new TypeError(
      `expireIn must be a non-negative integer: received ${expireIn}`,
    );
  }
}

const maxQueueBackoffIntervals = 5;
const maxQueueBackoffInterval = 60 * 60 * 1000;

function validateBackoffSchedule(backoffSchedule: number[]) {
  if (backoffSchedule.length > maxQueueBackoffIntervals) {
    throw new TypeError(
      `Invalid backoffSchedule, max ${maxQueueBackoffIntervals} intervals allowed`,
    );
  }
  for (let i = 0; i < backoffSchedule.length; ++i) {
    const interval = backoffSchedule[i];
    if (
      interval < 0 || interval > maxQueueBackoffInterval ||

View on GitHub (pinned to 9ad36f7a2c)