koala73/worldmonitor · error

EONET malformed event category

Error message

EONET malformed event category

What it means

Thrown by fetchEonet while iterating NASA EONET events: an event has no categories[0], so its category id cannot be normalized. The seeder requires every event to carry a category to classify it (and skip earthquakes), and treats its absence as a malformed payload rather than skipping the event.

Solutions

  1. Check the raw EONET API response for the offending event (curl the same URL) to confirm categories is missing vs a transient issue.
  2. Retry the seed — if the API returned a truncated/partial response, a fresh request often fixes it.
  3. Consider skipping events without a category instead of failing the whole seed, preserving the rest of the batch.
  4. Verify no intermediate cache/proxy is truncating the JSON payload.

Example fix

// before
const category = event.categories?.[0];
if (!category) throw new Error('EONET malformed event category');
// after
const category = event.categories?.[0];
if (!category) { console.warn('Skipping EONET event without category', event.id); continue; }
Defensive patterns

Strategy: type-guard

Validate before calling

const badEvents = (data?.events || []).filter(e => !e?.categories?.[0]); if (badEvents.length) console.warn('EONET events missing category:', badEvents.map(e => e.id));

Type guard

function hasCategory(event) { return Array.isArray(event?.categories) && event.categories.length > 0 && event.categories[0] != null; }

Try / catch

try { const events = await fetchEonet(fetchFn); } catch (e) { if (e.message === 'EONET malformed event category') { console.error('EONET payload missing categories; inspect raw response before retrying'); } throw e; }

Prevention

When it happens

Trigger: Any event object in data.events whose categories array is missing, null, or empty (event.categories?.[0] is undefined).

Common situations: EONET API schema change or partial outage returning events without categories; caching/proxy layers serving truncated GeoJSON; a new EONET event type that does not populate categories yet.

Understand the failure class

Related errors


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

Appendix: source

Thrown at scripts/seed-natural-events.mjs:253

        retryAfterMs: cause.retryAfterMs,
      });
      if (deadline - performance.now() <= Math.max(500, error.retryAfterMs || 0)) error.nonRetryable = true;
      throw error;
    } finally {
      await dispatcher?.destroy();
    }
  }, 1, 500);
}

async function fetchEonet(days, fetchFn = globalThis.fetch, now = Date.now()) {
  const url = `${EONET_API_URL}?status=open&days=${days}`;
  const data = await fetchEventSourceJson('eonet', url, fetchFn);
  if (!Array.isArray(data?.events)) throw new Error('EONET malformed response');
  const events = [];

  for (const event of data.events || []) {
    const category = event.categories?.[0];
    if (!category) throw new Error('EONET malformed event category');
    const normalizedCategory = normalizeCategory(category.id);
    if (normalizedCategory === 'earthquakes') continue;

    const latestGeo = event.geometry?.[event.geometry.length - 1];
    if (!latestGeo || !['Point', 'Polygon'].includes(latestGeo.type) || !Array.isArray(latestGeo.coordinates)) {
      throw new Error('EONET malformed event geometry');
    }
    if (latestGeo.type !== 'Point') continue;

    const eventDate = new Date(latestGeo.date);
    const [lon, lat] = latestGeo.coordinates;

    if (normalizedCategory === 'wildfires' && now - eventDate.getTime() > WILDFIRE_MAX_AGE_MS) continue;

    const source = event.sources?.[0];
    events.push({
      id: event.id || '',
      title: event.title || '',

View on GitHub (pinned to e586b8b4b8)