{"record":{"id":"355407fed4d67228","repo":"can1357/oh-my-pi","slug":"at-least-one-of-max-maxsize-or-ttl-is-required","errorCode":null,"errorMessage":"At least one of max, maxSize, or ttl is required","messagePattern":"At least one of max, maxSize, or ttl is required","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/lru.ts","lineNumber":56,"sourceCode":"\treadonly #entries = new Map<K, Entry<V>>();\n\treadonly #max: number;\n\treadonly #maxSize: number;\n\treadonly #maxEntrySize: number;\n\treadonly #sizeCalculation: ((value: V, key: K) => number) | undefined;\n\treadonly #ttl: number;\n\treadonly #updateAgeOnGet: boolean;\n\treadonly #dispose: ((value: V, key: K, reason: DisposeReason) => void) | undefined;\n\t#calculatedSize = 0;\n\n\t/** Creates an empty cache. */\n\tconstructor(options: LRUCacheOptions<K, V>) {\n\t\tthis.#max = positiveInteger(options.max, \"max\");\n\t\tthis.#maxSize = positiveInteger(options.maxSize, \"maxSize\");\n\t\tconst explicitMaxEntrySize = positiveInteger(options.maxEntrySize, \"maxEntrySize\");\n\t\tthis.#maxEntrySize = explicitMaxEntrySize || this.#maxSize;\n\t\tthis.#ttl = positiveInteger(options.ttl, \"ttl\");\n\t\tif (this.#max === 0 && this.#maxSize === 0 && this.#ttl === 0) {\n\t\t\tthrow new TypeError(\"At least one of max, maxSize, or ttl is required\");\n\t\t}\n\t\tif ((this.#maxSize !== 0 || this.#maxEntrySize !== 0) && options.sizeCalculation === undefined) {\n\t\t\tthrow new TypeError(\"sizeCalculation is required when a size limit is set\");\n\t\t}\n\t\tthis.#sizeCalculation = options.sizeCalculation;\n\t\tthis.#updateAgeOnGet = options.updateAgeOnGet === true;\n\t\tthis.#dispose = options.dispose;\n\t}\n\n\t/** Number of entries, including stale entries not yet removed by `get`. */\n\tget size(): number {\n\t\treturn this.#entries.size;\n\t}\n\n\t/** Aggregate calculated size of retained entries. */\n\tget calculatedSize(): number {\n\t\treturn this.#calculatedSize;\n\t}","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/lru.ts#L38-L74","documentation":"An LRUCache must be bounded in some way: by entry count (max), by total size (maxSize), or by lifetime (ttl). Constructing one with none of these (all absent or 0) throws this TypeError, because an unbounded LRU would silently grow forever — the library refuses to create a cache that provides no eviction policy.","triggerScenarios":"new LRUCache({}) or new LRUCache({ maxEntrySize: 10, sizeCalculation }) with no max/maxSize/ttl, or building options from config where all limit keys are missing/0 (e.g. max: parseInt(undefined) → NaN path already caught, but explicit { max: 0, maxSize: 0, ttl: 0 }).","commonSituations":"Config-driven construction where the user left all limits blank, refactored code that moved limits into a defaults object that was never merged, or tests copying a options object and deleting the limit keys.","solutions":["Pass at least one bound: new LRUCache({ max: 100 }) is the simplest fix.","For size-based bounding, set maxSize plus a sizeCalculation function.","For time-based expiry, set ttl (milliseconds), optionally with updateAgeOnGet.","Validate merged config before construction and apply a sane default limit when none is configured."],"exampleFix":"// before\nconst cache = new LRUCache({}); // TypeError: At least one of max, maxSize, or ttl is required\n// after\nconst cache = new LRUCache({ max: 1000 });","handlingStrategy":"validation","validationCode":"function assertBounded(o: { max?: number; maxSize?: number; ttl?: number }): void {\n  if (!o.max && !o.maxSize && !o.ttl) throw new TypeError(\"LRU options need at least one of max, maxSize, ttl\");\n}","typeGuard":"function isBounded(o: { max?: number; maxSize?: number; ttl?: number }): boolean {\n  return Boolean(o.max || o.maxSize || o.ttl);\n}","tryCatchPattern":"try {\n  cache = new LRUCache(options);\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes(\"At least one of max, maxSize, or ttl\")) {\n    logger.warn(\"Unbounded LRU options rejected; applying default bound\");\n    cache = new LRUCache({ ...options, max: 1000 });\n  } else throw err;\n}","preventionTips":["Always set an explicit max as a safety default in config-driven setups.","After merging/parsing config, assert at least one bound exists before constructing.","Never build options objects by deleting limit keys; construct from validated inputs."],"tags":["lru-cache","validation","configuration"],"backgroundTag":"missing-required-option","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}