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: stringView on GitHub (pinned to 1a708ee577)
Solutions
- Store the large value as TEXT or handle it as bigint end-to-end — do not round-trip it through `number`.
- Use `nonNegativeIntegerFromBigInt` only after confirming the value fits in the safe range; otherwise keep it as bigint.
- If the column represents a timestamp, switch to milliseconds (or store as TEXT ISO-8601).
- 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
- Do not store nanosecond timestamps or 64-bit counters as JS numbers.
- Keep `safeIntegers(true)` on for big columns and handle them as bigint throughout.
- Add schema CHECK constraints that keep INTEGER columns inside the safe range when they will be read as numbers.
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
- current exceeds the signed int64 range
- ${label} must be a non-negative bigint
- ${label} exceeds the JavaScript safe integer range
- samples[${index}].flags exceeds the SQLite bound
- taskId must be a string
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/c2d0654b3b729f59.
Report an issue: GitHub.