koala73/worldmonitor · error · Error

${chokepoint.id}: editorialLinks must be an array

Error message

${chokepoint.id}: editorialLinks must be an array

What it means

buildChokepointPageLinks() in the crawlable-corpus build script reads per-chokepoint content declarations (default: the CHOKEPOINT_CONTENT map) and expects declaration.editorialLinks to be an array of {href,label} objects. This throw is a build-time schema validation: if editorialLinks is present but not an array (e.g. an object, string, or number), the script refuses to continue so no corrupt chokepoint pages are rendered. An absent, undefined, or null key defaults to [] via ??, so only a non-nullish non-array value triggers this.

Solutions

  1. Open the offending chokepoint declaration in the content source (CHOKEPOINT_CONTENT in scripts/build-crawlable-corpus.mjs or the content option passed by the caller) and wrap the value in an array.
  2. If content is produced by another tool, inspect and fix the producer to emit an array of {href,label}.
  3. Add a schema/lint step over the content declarations so shape drift fails before the corpus build.

Example fix

// before
editorialLinks: { href: '/blog/strait-of-hormuz', label: 'Hormuz explainer' }
// after
editorialLinks: [{ href: '/blog/strait-of-hormuz', label: 'Hormuz explainer' }]
Defensive patterns

Strategy: validation

Validate before calling

const links = content[chokepoint.id]?.editorialLinks;
if (links != null && !Array.isArray(links)) {
  throw new TypeError(`${chokepoint.id}: editorialLinks must be an array, got ${typeof links}`);
}

Type guard

const isValidEditorialLinks = (v) => v == null || (Array.isArray(v) && v.every((l) => !!l && typeof l.href === 'string' && typeof l.label === 'string'));

Prevention

When it happens

Trigger: Calling buildChokepointPageLinks({ chokepoints, countries, crises, blogPostPaths, content }) where content[chokepoint.id].editorialLinks is set to a truthy non-array value such as an object, string, number, or boolean. Undefined and null do not trigger it (they fall back to []).

Common situations: A contributor writes editorialLinks as a single object instead of an array of one link; a merge or codegen tool flattens a single-element array into a scalar; a content-module refactor changes the declared shape; an external CMS export emits the wrong JSON type.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/9c598b0e66011bc0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-crawlable-corpus.mjs:1372

      routeIds: Array.isArray(entry.routeIds) ? [...entry.routeIds] : [],
      lat: Number(entry.lat),
      lon: Number(entry.lon),
      slug: slugify(entry.displayName || entry.id),
    }))
    .filter((entry) => entry.id && entry.displayName)
    .sort((a, b) => a.displayName.localeCompare(b.displayName));
}

export function buildChokepointPageLinks({ chokepoints, countries, crises, blogPostPaths = new Set(), content = CHOKEPOINT_CONTENT }) {
  const countryByCode = new Map(countries.map((country) => [country.code, country]));
  const crisisBySlug = new Map(crises.map((crisis) => [crisis.slug, crisis]));
  const byChokepointId = new Map();
  const byCountryCode = new Map();
  const byCrisisSlug = new Map();
  for (const chokepoint of chokepoints) {
    const declaration = content[chokepoint.id] || {};
    const editorialLinks = declaration.editorialLinks ?? [];
    if (!Array.isArray(editorialLinks)) throw new Error(`${chokepoint.id}: editorialLinks must be an array`);
    const editorial = new Map();
    for (const link of editorialLinks) {
      if (!blogPostPaths.has(link?.href) || typeof link.label !== 'string' || !link.label.trim()) {
        throw new Error(`${chokepoint.id}: editorialLinks requires a canonical blog post path and label: ${link?.href}`);
      }
      if (!editorial.has(link.href)) editorial.set(link.href, link);
    }
    const resolve = (field, targets, inverse) => {
      const ids = declaration[field] ?? [];
      if (!Array.isArray(ids)) throw new Error(`${chokepoint.id}: ${field} must be an array`);
      return [...new Set(ids)].map((id) => {
        const target = targets.get(id);
        if (!target) throw new Error(`${chokepoint.id}: ${field} contains unknown target ${id}`);
        const entries = inverse.get(id) || [];
        entries.push(chokepoint);
        inverse.set(id, entries);
        return target;
      });

View on GitHub (pinned to 7d06c8633d)