{"record":{"id":"01ea81f978dfd04f","repo":"agalwood/Motrix","slug":"label-must-be-non-negative","errorCode":null,"errorMessage":"${label} must be non-negative","messagePattern":"(.+?) must be non-negative","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"src/core/lib/sqlite-integers.ts","lineNumber":31,"sourceCode":"  if (typeof value !== 'bigint') {\n    throw new RangeError(`${label} is not an integer`)\n  }\n  if (\n    value < BigInt(Number.MIN_SAFE_INTEGER) ||\n    value > BigInt(Number.MAX_SAFE_INTEGER)\n  ) {\n    throw new RangeError(`${label} exceeds the JavaScript safe integer range`)\n  }\n  return Number(value)\n}\n\nexport function nonNegativeIntegerFromBigInt(\n  value: bigint,\n  label: string\n): number {\n  const converted = safeIntegerFromSql(value, label)\n  if (converted < 0) {\n    throw new RangeError(`${label} must be non-negative`)\n  }\n  return converted\n}\n\nexport function requireSafeTimestamp(value: number, label: string): void {\n  if (!Number.isSafeInteger(value)) {\n    throw new RangeError(`${label} must be a safe integer timestamp`)\n  }\n}\n\nexport function requireSafePositiveTimestamp(\n  value: number,\n  label: string\n): void {\n  if (!Number.isSafeInteger(value) || value <= 0) {\n    throw new RangeError(`${label} must be a positive safe integer timestamp`)\n  }\n}","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/lib/sqlite-integers.ts#L13-L49","documentation":"Thrown by nonNegativeIntegerFromBigInt after safeIntegerFromSql succeeds but the resulting number is negative. The helper enforces a non-negative contract (counts, sizes, rowids) on top of the safe-integer conversion.","triggerScenarios":"Calling nonNegativeIntegerFromBigInt(value, label) where value is a negative bigint or a negative in-range number; e.g. a column storing a signed delta that legitimately went below zero, or a corrupted rowid.","commonSituations":"A count/size/offset column that received a negative value due to a buggy increment; signed-vs-unsigned mismatch when importing data; underflow in a decrement operation that was written back to the DB.","solutions":["Inspect the row that triggered it and find the writer that produced the negative value.","Add a CHECK (col >= 0) constraint at the SQLite schema level to prevent future bad writes.","If negatives are valid for this field, call safeIntegerFromSql directly instead of the non-negative variant.","Clamp at the write site: UPDATE ... SET col = MAX(0, col - ?)."],"exampleFix":"// before\nconst count = nonNegativeIntegerFromBigInt(row.delta, 'delta')\n// after — field is genuinely signed\nconst delta = safeIntegerFromSql(row.delta, 'delta')","handlingStrategy":"validation","validationCode":"function isNonNegativeSafeInteger(value: bigint): boolean {\n  return value >= 0n && value <= BigInt(Number.MAX_SAFE_INTEGER)\n}\nif (!isNonNegativeSafeInteger(row.count)) {\n  throw new Error(`count out of range: ${row.count}`)\n}","typeGuard":"function isNonNegativeSafe(value: unknown): value is number {\n  if (typeof value === 'bigint') return value >= 0n && value <= BigInt(Number.MAX_SAFE_INTEGER)\n  if (typeof value === 'number') return Number.isSafeInteger(value) && value >= 0\n  return false\n}","tryCatchPattern":"try {\n  const count = nonNegativeIntegerFromBigInt(row.count, 'count')\n} catch (e) {\n  if (e instanceof RangeError && /non-negative/.test(e.message)) {\n    // log and clamp or reject the row\n  } else throw e\n}","preventionTips":["Add CHECK (col >= 0) constraints in SQLite for count/size/offset columns.","Audit decrement operations (UPDATE ... SET col = col - ?) and wrap with MAX(0, ...).","Validate at the write boundary, not just the read boundary."],"tags":["sqlite","type-coercion","data-integrity","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}