@haatlabs-core/provider-image-search
v0.10.0
Published
Image and web search through SearXNG, Brave or SerpApi behind one SearchProvider.
Readme
@haatlabs-core/provider-image-search
Install
pnpm add @haatlabs-core/provider-image-searchWhat it does
Image search through SearXNG (directly or through a services gateway), Brave or
SerpApi, and web-page search through SearXNG, behind one SearchProvider interface.
createImageSearchProvider(config, deps) and createWebSearchProvider wrap the provider in
the rate-limit gate, the circuit breaker and retry (protect, @haatlabs-core/providers), and with
deps.cache in a cachedCall layer outside the gate, so a hit spends no token.
Options
config: provider ('searxng' | 'brave' | 'serpapi'), timeoutMs, and that provider's
searxngBaseUrl and gatewayKey, braveApiKey or serpApiKey (named without its key, it
fails at construction). deps: fetch, limiter and rateLimit (the gate), sleep,
now, and cache ({ store, ttlMs, onEvent }; a null store computes every time). Each
search takes SearchOptions: limit, safeSearch (0-2), language, signal, call.
Errors
SEARCH_PROVIDER_ERROR (503) with a message saying what failed: a 429, a 5xx, no answer, a time
out and a body that is not JSON are transient (with retryAfterMs from Retry-After, seconds
or a date, at most five minutes); a 401 or 403 is permanent, naming what to check; any other
status is permanent. SerpApi's "Google hasn't returned any results" is no results; any other
SerpApi error is a transient "SerpApi could not run the search", its text not passed on. A
caller's abort is rethrown as theirs. A bad query or limit is the core VALIDATION_FAILED.
A project spreads IMAGE_SEARCH_ERRORS into defineErrors.
Gateway and deadlines
call (tier, priority, deadlineMs) goes to the gateway with a gatewayKey, tier 4 in the
background without one. Every provider's own time limit is timeoutMs, or call.deadlineMs
when that is sooner (localTimeoutMs), and the gateway is given that limit, never longer.
The deadline is each attempt's: a retry starts it again; pass signal for a hard limit on
the whole search. Through the gateway a refusal carries an UpstreamStatusError as its
cause: isGatewayRefusal(error) is true for the gateway's 401 and 403, and only its own
bad_request 400 is a contract error.
With deps.cache, a search that finds the same search already running waits for its answer
(cachedCall's single-flight), looking again for up to waitMs (10 s) before it searches
itself. That wait does not end on the caller's signal: an aborted search answers once it
is over, so waitMs bounds how late.
Fixtures
@haatlabs-core/provider-image-search/fixtures: searchFixtures.searxngImages is recorded from
SearXNG; braveImages and serpApiImages are written in the shapes Brave and SerpApi
document, not recorded, until the owner records them with a trial key.
Testing
pnpm --filter @haatlabs-core/provider-image-search test (unit, on the fixtures) and test:int
(a real SearXNG in a container; needs Docker). The live tests, with the owner's
BRAVE_API_KEY and SERPAPI_API_KEY, run with pnpm exec vitest run --config
vitest.real.config.ts and are skipped without the keys.
Hosted services
Without a search engine of your own, provider: 'haatlabs' with haatlabsApiKey (your
RapidAPI key) searches through the Haatlabs API on RapidAPI. A refusal is
SEARCH_PROVIDER_ERROR, transient as the API's was, with the API's error
(SERVICE_QUOTA_EXCEEDED, SERVICE_KEY_REFUSED, …) as its cause. See @haatlabs-core/services and
https://haatlabs.shop/docs/services/
