{"record":{"id":"af4517fb3f5ae661","repo":"can1357/oh-my-pi","slug":"sizecalculation-return-invalid-expect-positive-in","errorCode":null,"errorMessage":"sizeCalculation return invalid (expect positive integer)","messagePattern":"sizeCalculation return invalid \\(expect positive integer\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/lru.ts","lineNumber":172,"sourceCode":"\t\t\tconst [key, entry] = entries[index]!;\n\t\t\tif (!this.#isStale(entry)) yield key;\n\t\t}\n\t}\n\n\t/** Iterates fresh values from most to least recently used. */\n\t*values(): Generator<V, void, unknown> {\n\t\tconst entries = [...this.#entries.values()];\n\t\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\t\tconst entry = entries[index]!;\n\t\t\tif (!this.#isStale(entry)) yield entry.value;\n\t\t}\n\t}\n\n\t#entrySize(value: V, key: K): number {\n\t\tif (this.#sizeCalculation === undefined) return 0;\n\t\tconst size = this.#sizeCalculation(value, key);\n\t\tif (!Number.isInteger(size) || size <= 0)\n\t\t\tthrow new TypeError(\"sizeCalculation return invalid (expect positive integer)\");\n\t\treturn size;\n\t}\n\n\t#isStale(entry: Entry<V>): boolean {\n\t\treturn this.#ttl !== 0 && performance.now() - entry.start > this.#ttl;\n\t}\n\n\t#remove(key: K, entry: Entry<V>, reason: DisposeReason): void {\n\t\tthis.#dispose?.(entry.value, key, reason);\n\t\tthis.#entries.delete(key);\n\t\tthis.#calculatedSize -= entry.size;\n\t}\n}\n","sourceCodeStart":154,"sourceCodeEnd":186,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/lru.ts#L154-L186","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Clamp in the callback: return Math.max(1, Math.ceil(rawSize)).","Use Math.round/floor on computed byte sizes to guarantee an integer.","Handle empty values explicitly — treat them as size 1 so they still count against maxSize.","Make the callback total: guard against undefined/foreign value shapes and return a fallback size instead of NaN."],"exampleFix":"// before\nsizeCalculation: (v: string) => v.length // empty string → 0 → TypeError\n// after\nsizeCalculation: (v: string) => Math.max(1, Buffer.byteLength(v));","handlingStrategy":"validation","validationCode":"function safeSizeCalculation(measure: (v: never, k: never) => number) {\n  return (v: unknown, k: unknown): number => {\n    const raw = measure(v as never, k as never);\n    return Number.isFinite(raw) ? Math.max(1, Math.ceil(raw)) : 1;\n  };\n}","typeGuard":"function isValidEntrySize(n: unknown): n is number {\n  return typeof n === \"number\" && Number.isInteger(n) && n > 0;\n}","tryCatchPattern":"try {\n  cache.set(key, value);\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes(\"sizeCalculation return invalid\")) {\n    logger.warn(\"sizeCalculation returned invalid size\", { key });\n    cacheWithSafeCalc.set(key, value); // cache built with clamped sizeCalculation\n  } else throw err;\n}","preventionTips":["Wrap every sizeCalculation with Math.max(1, Math.ceil(raw)) — empty values still occupy space.","Never assume v.length/v.size exists; coerce and validate inside the callback.","Test the callback against empty strings, empty objects, and null-ish values.","Keep arithmetic integer-only (byte counts, not bytes/1000 fractions)."],"tags":["lru-cache","callback","validation","type-error"],"backgroundTag":"invalid-callback-return-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}