can1357/oh-my-pi · error · TypeError

sizeCalculation return invalid (expect positive integer)

Error message

sizeCalculation return invalid (expect positive integer)

What it means

When a size-bounded LRUCache computes an entry's size, the sizeCalculation callback must return a positive integer (0 and negatives are rejected, as are NaN/fractions). A non-conforming return throws this TypeError from #entrySize, typically while adding an entry (set) or reading the cache's total size. This keeps total-size accounting valid — a zero or negative entry size would corrupt eviction decisions.

Source

Thrown at packages/utils/src/lru.ts:172

			const [key, entry] = entries[index]!;
			if (!this.#isStale(entry)) yield key;
		}
	}

	/** Iterates fresh values from most to least recently used. */
	*values(): Generator<V, void, unknown> {
		const entries = [...this.#entries.values()];
		for (let index = entries.length - 1; index >= 0; index--) {
			const entry = entries[index]!;
			if (!this.#isStale(entry)) yield entry.value;
		}
	}

	#entrySize(value: V, key: K): number {
		if (this.#sizeCalculation === undefined) return 0;
		const size = this.#sizeCalculation(value, key);
		if (!Number.isInteger(size) || size <= 0)
			throw new TypeError("sizeCalculation return invalid (expect positive integer)");
		return size;
	}

	#isStale(entry: Entry<V>): boolean {
		return this.#ttl !== 0 && performance.now() - entry.start > this.#ttl;
	}

	#remove(key: K, entry: Entry<V>, reason: DisposeReason): void {
		this.#dispose?.(entry.value, key, reason);
		this.#entries.delete(key);
		this.#calculatedSize -= entry.size;
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Clamp in the callback: return Math.max(1, Math.ceil(rawSize)).
  2. Use Math.round/floor on computed byte sizes to guarantee an integer.
  3. Handle empty values explicitly — treat them as size 1 so they still count against maxSize.
  4. Make the callback total: guard against undefined/foreign value shapes and return a fallback size instead of NaN.

Example fix

// before
sizeCalculation: (v: string) => v.length // empty string → 0 → TypeError
// after
sizeCalculation: (v: string) => Math.max(1, Buffer.byteLength(v));
Defensive patterns

Strategy: validation

Validate before calling

function safeSizeCalculation(measure: (v: never, k: never) => number) {
  return (v: unknown, k: unknown): number => {
    const raw = measure(v as never, k as never);
    return Number.isFinite(raw) ? Math.max(1, Math.ceil(raw)) : 1;
  };
}

Type guard

function isValidEntrySize(n: unknown): n is number {
  return typeof n === "number" && Number.isInteger(n) && n > 0;
}

Try / catch

try {
  cache.set(key, value);
} catch (err) {
  if (err instanceof TypeError && err.message.includes("sizeCalculation return invalid")) {
    logger.warn("sizeCalculation returned invalid size", { key });
    cacheWithSafeCalc.set(key, value); // cache built with clamped sizeCalculation
  } else throw err;
}

Prevention

When it happens

Trigger: sizeCalculation returning 0 for empty strings/empty buffers, returning v.length when length can be 0, returning a fractional byte estimate, returning NaN (e.g. v.size undefined), or a callback with wrong assumptions about the value type (undefined.length).

Common situations: Caching empty strings or empty objects whose natural size is 0, byte-count helpers using values that aren't measured (Blob.size on a detached blob), math like bytes/1000 producing fractions, migrating from a cache library that allowed 0-size entries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/af4517fb3f5ae661. Report an issue: GitHub.