{"record":{"id":"87fcffe49cb1f62c","repo":"koala73/worldmonitor","slug":"cachekey","errorCode":null,"errorMessage":"${cacheKey}","messagePattern":"\\$\\{cacheKey\\}","errorType":"exception","errorClass":"SeedUnavailableError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/economic/v1/list-world-bank-indicators.ts","lineNumber":116,"sourceCode":"  req: ListWorldBankIndicatorsRequest,\n): Promise<ListWorldBankIndicatorsResponse> {\n  try {\n    // The exported client accepts generic indicator codes, not just the tech\n    // display catalogue. Bound their grammar without restricting that contract.\n    if (req.indicatorCode.length > 64 || !/^[A-Z0-9_]+(?:\\.[A-Z0-9_]+)+$/.test(req.indicatorCode)) {\n      return { data: [], pagination: undefined };\n    }\n    const country = normalizeCountries(req.countryCode);\n    if (country === null || !Number.isInteger(req.year)) return { data: [], pagination: undefined };\n    // Match the existing World Bank relay's maximum lookback.\n    const years = req.year > 0 ? Math.min(req.year, 30) : 5;\n    const currentYear = new Date().getFullYear();\n    const cacheKey = `${REDIS_CACHE_KEY}:${req.indicatorCode}:${country || '__default__'}:${years}:${currentYear}`;\n    const result = await cachedFetchJsonWithMeta<ListWorldBankIndicatorsResponse>(cacheKey, REDIS_CACHE_TTL, async () => {\n      const data = await fetchWorldBankIndicators(req.indicatorCode, country || TECH_COUNTRIES.join(';'), years, currentYear);\n      return { data, pagination: undefined };\n    }, 120, { cacheFailures: false });\n    if (!result.data || !Array.isArray(result.data.data)) throw new SeedUnavailableError(cacheKey);\n    return result.data;\n  } catch {\n    throw new SeedUnavailableError(REDIS_CACHE_KEY);\n  }\n}\n","sourceCodeStart":98,"sourceCodeEnd":122,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/economic/v1/list-world-bank-indicators.ts#L98-L122","documentation":"SeedUnavailableError thrown by listWorldBankIndicators when the cachedFetchJsonWithMeta result is missing or its data.data field is not an array — i.e. the refresh callback returned an unexpected shape. The specific cacheKey (including indicator, countries, years, current year) is in the message.","triggerScenarios":"fetchWorldBankIndicators resolves but returns a value without a valid data array, or cachedFetchJsonWithMeta returns a stale/failed result the code treats as invalid; typically follows an upstream World Bank problem.","commonSituations":"World Bank API outage during cache refresh; a refactor changing the response shape of fetchWorldBankIndicators without updating this check; cache read returning a value typed differently than expected.","solutions":["Inspect the cacheKey in the message and check Redis for the stale/missing entry","Verify fetchWorldBankIndicators still returns { data: [...] } matching ListWorldBankIndicatorsResponse","Check recent refactors to either function for shape changes","Retry after upstream recovery and confirm the seed repopulates"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"if (!result?.data || !Array.isArray(result.data.data)) {\n  throw new Error(`bad cached shape for ${cacheKey}`);\n}","typeGuard":"function isValidIndicatorResponse(v: unknown): v is ListWorldBankIndicatorsResponse {\n  return !!v && typeof v === 'object' && Array.isArray((v as any).data);\n}","tryCatchPattern":"try {\n  const res = await api.listWorldBankIndicators(req);\n  if (!isValidIndicatorResponse(res)) throw new Error('shape');\n  return res;\n} catch {\n  return { data: [], pagination: undefined };\n}","preventionTips":["Type-check the refresh callback's return against the response type","Add a contract test for fetchWorldBankIndicators' shape","Version cached payloads so old shapes are invalidated","Check Redis entry for the specific cacheKey when this fires"],"tags":["cache","redis","response-shape","world-bank"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}