koala73/worldmonitor · error · SeedUnavailableError

${PHYSICAL_PREMIUM_KEY}

Error message

${PHYSICAL_PREMIUM_KEY}

What it means

getPhysicalPremiums throws SeedUnavailableError(PHYSICAL_PREMIUM_KEY) when readCachedJson reports an error status reading the physical premiums cache — i.e. Redis itself failed (connection, timeout, decode), not merely a miss. A clean miss instead degrades to an empty premiums list.

Solutions

  1. Verify Upstash URL/token via loadEnvFile and test connectivity with a PING
  2. Check logCacheReadError/readCachedJson error details for the Redis failure cause
  3. Retry the request — transient Redis errors clear on their own
  4. Confirm the seed producer is healthy so the key exists with a valid payload
Defensive patterns

Strategy: retry

Validate before calling

const probe = await redis.ping();
if (probe !== 'PONG') throw new Error('redis unreachable before physical premiums read');

Try / catch

try {
  return await api.getPhysicalPremiums(req);
} catch (e) {
  if (isSeedUnavailableError(e)) {
    // Redis read error: brief retry, then degrade to empty premiums
    return await withRetry(() => api.getPhysicalPremiums(req), { attempts: 2, fallback: { premiums: [] } });
  }
  throw e;
}

Prevention

When it happens

Trigger: Redis/Upstash connection failure, timeout, auth error, or deserialization error while reading PHYSICAL_PREMIUM_KEY; e.g. wrong credentials, Redis maintenance, network partition from the worker.

Common situations: Missing or rotated Upstash credentials on a new environment; Upstash free-tier eviction/limits; transient network blips during the read.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/d795070a70601b0d. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/market/v1/get-physical-premiums.ts:174

    }
    if (!metals.includes(metal)) metals.push(metal);
  }
  if (unsupported.length > 0) {
    throw new ValidationError([{
      field: 'metals',
      description: `Unsupported metal${unsupported.length === 1 ? '' : 's'}: ${unsupported.join(', ')}`,
    }]);
  }
  return metals;
}

export async function getPhysicalPremiums(
  _ctx: ServerContext,
  req: GetPhysicalPremiumsRequest,
): Promise<GetPhysicalPremiumsResponse> {
  const metals = resolvePhysicalPremiumMetals(parseStringArray(req.metals));
  const read = await readCachedJson(PHYSICAL_PREMIUM_KEY, true);
  if (read.status === 'error') throw new SeedUnavailableError(PHYSICAL_PREMIUM_KEY);
  try {
    const raw = read.status === 'hit' ? read.value as RawPayload | null : null;
    const fx = mapFx(raw?.fx);
    if (!fx || !Array.isArray(raw?.premiums)) return { premiums: [] };
    const premiums = raw.premiums.map((premium) => mapPremium(premium, fx.rate))
      .filter((item): item is PhysicalPremium => item !== null);
    if (premiums.length !== 2 || premiums.length !== raw.premiums.length) return { premiums: [] };
    if (new Set(premiums.map((premium) => premium.metal)).size !== 2) return { premiums: [] };
    const wanted = new Set(metals);
    return {
      premiums: metals.length === 0 ? premiums : premiums.filter((item) => wanted.has(item.metal)),
      fx,
    };
  } catch {
    return { premiums: [] };
  }
}

View on GitHub (pinned to e586b8b4b8)