can1357/oh-my-pi · error · TypeError
${name} must be a non-negative integer
Error message
${name} must be a non-negative integer What it means
LRUCache validates its numeric options (max, maxSize, maxEntrySize, ttl) through the positiveInteger helper: values must be undefined (treated as 0/disabled) or a non-negative integer. Passing a negative number, a fraction, NaN, Infinity, or a non-number type throws this TypeError naming the offending option.
Source
Thrown at packages/utils/src/lru.ts:32
/** Calculates an entry's size. */
sizeCalculation?: (value: V, key: K) => number;
/** Entry lifetime in milliseconds; zero disables expiry. */
ttl?: number;
/** Refreshes an entry's lifetime when it is read. */
updateAgeOnGet?: boolean;
/** Called synchronously before an entry is removed. */
dispose?: (value: V, key: K, reason: DisposeReason) => void;
}
interface Entry<V> {
value: V;
size: number;
start: number;
}
function positiveInteger(value: number | undefined, name: string): number {
if (value === undefined) return 0;
if (!Number.isInteger(value) || value < 0) throw new TypeError(`${name} must be a non-negative integer`);
return value;
}
/** A bounded least-recently-used cache with optional size and lifetime limits. */
export class LRUCache<K, V> {
readonly #entries = new Map<K, Entry<V>>();
readonly #max: number;
readonly #maxSize: number;
readonly #maxEntrySize: number;
readonly #sizeCalculation: ((value: V, key: K) => number) | undefined;
readonly #ttl: number;
readonly #updateAgeOnGet: boolean;
readonly #dispose: ((value: V, key: K, reason: DisposeReason) => void) | undefined;
#calculatedSize = 0;
/** Creates an empty cache. */
constructor(options: LRUCacheOptions<K, V>) {
this.#max = positiveInteger(options.max, "max");View on GitHub (pinned to 9690622007)
Solutions
- Coerce and validate before constructing: max = Number.parseInt(raw, 10) with a Number.isInteger check.
- Round computed sizes: Math.floor(bytes / 1024) so the value is an integer.
- Guard against NaN/Infinity: use Number.isFinite(v) && Number.isInteger(v) && v >= 0.
- Fix the config source producing the bad value (env var, JSON field, CLI flag).
Example fix
// before
const cache = new LRUCache({ max: Number(process.env.CACHE_MAX) }); // '100' or NaN → TypeError
// after
const raw = Number(process.env.CACHE_MAX);
const max = Number.isInteger(raw) && raw >= 0 ? raw : 100;
const cache = new LRUCache({ max }); Defensive patterns
Strategy: validation
Validate before calling
function toNonNegativeInt(raw: unknown): number {
const n = typeof raw === "number" ? raw : Number(raw);
if (!Number.isInteger(n) || n < 0) throw new TypeError(`limit must be a non-negative integer, got ${raw}`);
return n;
} Type guard
function isNonNegativeInt(v: unknown): v is number {
return typeof v === "number" && Number.isInteger(v) && v >= 0;
} Try / catch
try {
cache = new LRUCache(options);
} catch (err) {
if (err instanceof TypeError && err.message.includes("must be a non-negative integer")) {
logger.warn("Invalid LRU limit, falling back to defaults", { option: err.message.split(" ")[0] });
cache = new LRUCache({ max: 1000 });
} else throw err;
} Prevention
- parseInt/Number all limits coming from env/config before constructing.
- Use Math.floor on computed sizes to avoid fractions.
- Check Number.isFinite to exclude NaN/Infinity.
- Keep a typed options interface so string config values can't leak in.
When it happens
Trigger: new LRUCache({ max: -1 }), new LRUCache({ max: 10.5 }), new LRUCache({ ttl: Number.NaN }), new LRUCache({ max: '100' }) (string), or calling setter/option paths like explicitMaxEntrySize with a malformed value from config.
Common situations: Reading limits from env vars or config files without parseInt/Number conversion ('100' string), arithmetic producing NaN (undefined * 1024), fractions from byte-size division (maxSize = bytes/1024 yielding 1.5), or negated defaults.
Related errors
- At least one of max, maxSize, or ttl is required
- sizeCalculation is required when a size limit is set
- sizeCalculation return invalid (expect positive integer)
- Invalid pattern: {err}
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains an empty provider
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a7c66708db9e4790.
Report an issue: GitHub.