koala73/worldmonitor · error

ExaSearchAdapter does not support single-product parsing

Error message

ExaSearchAdapter does not support single-product parsing

What it means

ExaSearchAdapter is a search-mode adapter: its fetchTarget() already produces parsed products straight from Exa results (with optional Firecrawl fallback extraction), so parseProduct() has nothing to do and throws unconditionally. Seeing this error means the pipeline treated the adapter like a page-scraping adapter (fetch then parse) — a protocol mismatch, not a data problem.

Source

Thrown at consumer-prices-core/src/adapters/exa-search.ts:317

            inStock: true,
            rawPayload: {
              exaUrl: payload.firecrawlUrl,
              firecrawlFallback: true,
              basketSlug: payload.basketSlug,
              itemCategory: payload.itemCategory,
              canonicalName: payload.canonicalName,
            },
          },
        ];
      }
      ctx.logger.warn(`  [firecrawl-fallback] ${payload.canonicalName}: no ${currency} price found in Firecrawl markdown either`);
    }

    return [];
  }

  async parseProduct(_ctx: AdapterContext, _result: FetchResult): Promise<ParsedProduct> {
    throw new Error('ExaSearchAdapter does not support single-product parsing');
  }

  async validateConfig(config: RetailerConfig): Promise<string[]> {
    const errors: string[] = [];
    if (!this.apiKey) errors.push('EXA_API_KEY env var is required for adapter: exa-search');
    if (!config.baseUrl) errors.push('baseUrl is required');
    return errors;
  }
}

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Remove the parse step for retailers using exa-search — products arrive already parsed from fetchTarget
  2. Dispatch on adapter capability: search-mode adapters skip parseProduct entirely
  3. Add an explicit adapter kind ('search' vs 'scrape') to the contract so runners branch on it instead of discovering this by exception

Example fix

// before — assumes every adapter is fetch-then-parse
const result = await adapter.fetchTarget(ctx, target);
const product = await adapter.parseProduct(ctx, result);

// after — branch on adapter kind
const result = await adapter.fetchTarget(ctx, target);
const product = adapter.key === 'exa-search'
  ? productsFromSearchResult(result) // already parsed inside the search payload
  : await adapter.parseProduct(ctx, result);
Defensive patterns

Strategy: type-guard

Validate before calling

// when assembling pipeline stages
function requiresParseStage(adapter: RetailerAdapter): boolean {
  return adapter.key !== 'exa-search'; // search-mode adapters finish parsing in fetchTarget
}
if (requiresParseStage(adapter)) stages.push(parseStage);

Type guard

function isSearchModeAdapter(a: RetailerAdapter): boolean {
  return a.key === 'exa-search'; // parseProduct() throws by design on this adapter
}

Try / catch

try {
  product = await adapter.parseProduct(ctx, result);
} catch (err) {
  if (err instanceof Error && err.message.includes('does not support single-product parsing')) {
    product = productsFromSearchResult(result); // search adapters embed parsed products in the payload
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: A generic runner calling adapter.parseProduct(ctx, result) after fetchTarget() because the retailer config selected a scrape-style pipeline; harness code that round-trips every adapter through fetch+parse; new code assuming RetailerAdapter always implements parseProduct.

Common situations: Adding exa-search to a retailer whose pipeline stage list still includes a parse step; test suites iterating all adapters with a universal fetch-then-parse contract; a refactor moving parsing into a shared step without checking adapter capability.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/d3faaccdf7deeef6. Report an issue: GitHub.