{"record":{"id":"79b631e421283232","repo":"remix-run/remix","slug":"files-cache-must-implement-the-filestorage-interfa","errorCode":null,"errorMessage":"files.cache must implement the FileStorage interface","messagePattern":"files\\.cache must implement the FileStorage interface","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/assets/src/lib/files/config.ts","lineNumber":302,"sourceCode":"        transform.extensions,\n        `files.globalTransforms[${index}].extensions`,\n      ),\n    })\n  }\n\n  let maxRequestTransforms = files.maxRequestTransforms ?? defaultMaxRequestTransforms\n  if (!Number.isInteger(maxRequestTransforms) || maxRequestTransforms < 1) {\n    throw new TypeError('files.maxRequestTransforms must be a positive integer')\n  }\n\n  if (files.cache !== undefined) {\n    if (\n      files.cache === null ||\n      typeof files.cache !== 'object' ||\n      typeof files.cache.get !== 'function' ||\n      typeof files.cache.set !== 'function'\n    ) {\n      throw new TypeError('files.cache must implement the FileStorage interface')\n    }\n  }\n\n  return {\n    cache: files.cache,\n    extensions: normalizedExtensions,\n    globalTransforms: normalizedGlobalTransforms,\n    hasTransforms: normalizedTransforms.size > 0 || normalizedGlobalTransforms.length > 0,\n    maxRequestTransforms,\n    transforms: normalizedTransforms,\n  }\n}\n\nfunction normalizeTransformExtensions(\n  extensions: readonly string[] | undefined,\n  optionPath: string,\n): readonly string[] | undefined {\n  if (extensions === undefined) return undefined","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/assets/src/lib/files/config.ts#L284-L320","documentation":"When `files.cache` is provided it must implement the FileStorage interface (objects with `get` and `set` functions). The library duck-types the option at normalization time rather than trusting the type system, because users often pass plain objects or partially-implemented caches.","triggerScenarios":"Passing `files: { cache: {} }`, a Map instance, a cache module namespace without get/set, or null to resolveAssetServerOptions/normalizeFilesOptions.","commonSituations":"Trying to use a custom in-memory LRU or a plain object literal as cache, wrapping an existing cache library whose methods are named differently (e.g. read/write), or passing a cache that was serialized/structured-cloned so methods were lost.","solutions":["Implement both get and set methods on the cache object","If wrapping an external cache, adapt its method names: `get: (k) => store.read(k), set: (k, v) => store.write(k, v)`","Omit `cache` to use the default cache behavior"],"exampleFix":"// before\nfiles: { cache: {} }\n// after\nfiles: {\n  cache: {\n    get(key) { return myStore[key] },\n    set(key, value) { myStore[key] = value },\n  },\n}","handlingStrategy":"type-guard","validationCode":"const c = options.files?.cache\nif (c && (typeof c.get !== 'function' || typeof c.set !== 'function')) throw new Error('cache must implement FileStorage')","typeGuard":"function isFileStorage(c: unknown): c is FileStorage {\n  return c != null && typeof c === 'object'\n    && typeof (c as FileStorage).get === 'function'\n    && typeof (c as FileStorage).set === 'function'\n}","tryCatchPattern":null,"preventionTips":["Implement FileStorage as a small class or object literal with get/set","Add a unit test asserting your cache satisfies the interface"],"tags":["cache","validation","assets","duck-typing"],"backgroundTag":"invalid-option-value","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}