can1357/oh-my-pi · error · TypeError

sizeCalculation is required when a size limit is set

Error message

sizeCalculation is required when a size limit is set

What it means

When an LRUCache is given a size limit (maxSize or maxEntrySize > 0), it needs a sizeCalculation callback to determine each entry's size — the library cannot infer it. Constructing a size-bounded cache without that function throws this TypeError at construction time rather than failing later on the first set().

Source

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

	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");
		this.#maxSize = positiveInteger(options.maxSize, "maxSize");
		const explicitMaxEntrySize = positiveInteger(options.maxEntrySize, "maxEntrySize");
		this.#maxEntrySize = explicitMaxEntrySize || this.#maxSize;
		this.#ttl = positiveInteger(options.ttl, "ttl");
		if (this.#max === 0 && this.#maxSize === 0 && this.#ttl === 0) {
			throw new TypeError("At least one of max, maxSize, or ttl is required");
		}
		if ((this.#maxSize !== 0 || this.#maxEntrySize !== 0) && options.sizeCalculation === undefined) {
			throw new TypeError("sizeCalculation is required when a size limit is set");
		}
		this.#sizeCalculation = options.sizeCalculation;
		this.#updateAgeOnGet = options.updateAgeOnGet === true;
		this.#dispose = options.dispose;
	}

	/** Number of entries, including stale entries not yet removed by `get`. */
	get size(): number {
		return this.#entries.size;
	}

	/** Aggregate calculated size of retained entries. */
	get calculatedSize(): number {
		return this.#calculatedSize;
	}

	/** Stores a value and makes it most recently used. */
	set(key: K, value: V | undefined): this {

View on GitHub (pinned to 9690622007)

Solutions

  1. Provide sizeCalculation: (value, key) => byteLength or similar when setting maxSize/maxEntrySize.
  2. If you don't need size bounding, remove maxSize/maxEntrySize and bound with max or ttl instead.
  3. For string/buffer caches a typical calculation is v => Buffer.byteLength(String(v)).
  4. Keep options objects in memory — don't serialize cache options through JSON.

Example fix

// before
const cache = new LRUCache({ maxSize: 1024 * 1024 }); // TypeError
// after
const cache = new LRUCache({
  maxSize: 1024 * 1024,
  sizeCalculation: (v: string) => Buffer.byteLength(v),
});
Defensive patterns

Strategy: validation

Validate before calling

function assertSizeConfig(o: { maxSize?: number; maxEntrySize?: number; sizeCalculation?: (v: unknown, k: unknown) => number }): void {
  if ((o.maxSize || o.maxEntrySize) && typeof o.sizeCalculation !== "function") {
    throw new TypeError("sizeCalculation function is required with maxSize/maxEntrySize");
  }
}

Type guard

function hasSizeCalculation(o: { maxSize?: number; maxEntrySize?: number; sizeCalculation?: unknown }): o is { sizeCalculation: (v: never, k: never) => number } & Record<string, unknown> {
  return Boolean(o.maxSize || o.maxEntrySize) && typeof o.sizeCalculation === "function";
}

Try / catch

try {
  cache = new LRUCache(options);
} catch (err) {
  if (err instanceof TypeError && err.message.includes("sizeCalculation is required")) {
    logger.warn("Size-bounded LRU missing sizeCalculation; adding byte-length estimator");
    cache = new LRUCache({ ...options, sizeCalculation: (v: unknown) => Math.max(1, Buffer.byteLength(String(v))) });
  } else throw err;
}

Prevention

When it happens

Trigger: new LRUCache({ maxSize: 1024 }) with no sizeCalculation, new LRUCache({ maxEntrySize: 100 }) alone, or config-merge dropping the sizeCalculation key while keeping maxSize.

Common situations: Spreading options objects where functions were serialized/dropped (JSON round-trip of options), copy-pasted cache setup that kept maxSize but removed the callback, TypeScript type loosening via as any that bypassed the option type check.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


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