agalwood/Motrix · error · RangeError

${label} exceeds the JavaScript safe integer range

Error message

${label} exceeds the JavaScript safe integer range

What it means

`safeIntegerFromSql` converts a value read from a better-sqlite3 row (under `safeIntegers()` mode, where big ints arrive as `bigint`) into a JavaScript `number`. It throws this `RangeError` when a stored `INTEGER` column holds a value outside `+/-2^53-1` — a SQLite integer too large to represent precisely as a JS number. The `label` identifies the column or field for diagnostics.

Source

Thrown at src/core/lib/sqlite-integers.ts:9

/**
 * Conversions between better-sqlite3 `safeIntegers()` rows and JavaScript
 * numbers, shared by the SQLite-backed stores.
 */

export function safeIntegerFromSql(value: unknown, label: string): number {
  if (typeof value === 'number') {
    if (!Number.isSafeInteger(value)) {
      throw new RangeError(`${label} exceeds the JavaScript safe integer range`)
    }
    return value
  }
  if (typeof value !== 'bigint') {
    throw new RangeError(`${label} is not an integer`)
  }
  if (
    value < BigInt(Number.MIN_SAFE_INTEGER) ||
    value > BigInt(Number.MAX_SAFE_INTEGER)
  ) {
    throw new RangeError(`${label} exceeds the JavaScript safe integer range`)
  }
  return Number(value)
}

export function nonNegativeIntegerFromBigInt(
  value: bigint,
  label: string

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Store the large value as TEXT or handle it as bigint end-to-end — do not round-trip it through `number`.
  2. Use `nonNegativeIntegerFromBigInt` only after confirming the value fits in the safe range; otherwise keep it as bigint.
  3. If the column represents a timestamp, switch to milliseconds (or store as TEXT ISO-8601).
  4. Add a CHECK constraint at the SQLite layer to prevent out-of-range writes.

Example fix

// before
const occurredAt = safeIntegerFromSql(row.occurredAtNs, 'occurredAt')  // ns > 2^53
// after
const occurredAt = Math.floor(Number(row.occurredAtNs / 1_000_000n))  // ns -> ms
Defensive patterns

Strategy: validation

Validate before calling

function fitsSafeInteger(v: bigint | number): boolean {
  if (typeof v === 'number') return Number.isSafeInteger(v)
  return v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)
}

Type guard

function isSafeIntegerValue(v: unknown): v is number {
  if (typeof v === 'number') return Number.isSafeInteger(v)
  if (typeof v === 'bigint')
    return v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)
  return false
}

Try / catch

try {
  safeIntegerFromSql(row.col, 'col')
} catch (err) {
  if (err instanceof RangeError && err.message.endsWith('exceeds the JavaScript safe integer range')) {
    // keep as bigint or re-scale units instead of converting
  } else throw err
}

Prevention

When it happens

Trigger: Reading a SQLite column whose stored value exceeds `Number.MAX_SAFE_INTEGER` — e.g. a high-resolution epoch-nanosecond timestamp, a 64-bit counter that grew past 2^53, or a row written under a schema that used INTEGER for a value better stored as TEXT/bigint. Fires whether the value arrived as a JS number (unsafe) or as a bigint (out of range).

Common situations: Persisting nanosecond timestamps in an INTEGER column; counters that legitimately exceed 2^53 (lifetime byte counts after a long run); better-sqlite3 with `safeIntegers(true)` returning bigints for columns that were previously small numbers; schema migrations that widened a column's value range.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/c2d0654b3b729f59. Report an issue: GitHub.