The client

Call the Query API from any storefront with typed requests and answers, and read and write a listing's URL.

@orbsearch/client calls the Query API with typed requests and answers. It needs no framework, and the React components build on it:

app/search.ts
import {  } from "@orbsearch/client";

export const  = ({ : "http://127.0.0.1:7800" });

const  = await .("graues Sofa");
const  = await .({ : "SOFA-ASKA-2" });
const  = await .("/wohnen/sofas?color=grey");

The calls search for "graues Sofa" (grey sofa), fetch one sofa and ask what the sofa page /wohnen/sofas (wohnen: living) with grey chosen is. Hover a call to read what it answers.

The packages are not on npm yet. A shop inside this repository's pnpm workspace depends on them as workspace:*, as the demo storefront in apps/demo does, and its bundler compiles their TypeScript source:

package.json (trimmed)
{
    "dependencies": {
        "@orbsearch/client": "workspace:*",
        "@orbsearch/react": "workspace:*"
    }
}

The calls

Each call is one request. The second argument is the request without its text, with filters in the shape a listing's URL holds, and the third cancels the call when the shopper moves on:

app/search.ts
const  = new ();
const  = await .(
    "",
    { : "category_page", : "wohnen/sofas", : { : ["grey"] }, : "price_asc" },
    { : . },
);
  • query searches or browses a category (Search and browse).
  • product fetches one product by id or url, or null when the channel sells none (Product pages).
  • resolve says what one of the merchant's URLs is: a listing with its results, a product, a redirect or nothing (URLs and redirects).
  • facets counts named facets in full, without products, for a facet that loads when opened.
  • suggestions returns the store a search box runs on (Autocomplete).

In the browser, registerAgentTools offers the same client to agents as WebMCP tools (Agents).

Reference: POST /v1/search · GET /v1/product · GET /v1/resolve

One client per page load

A client's searches name one page view, which Insights counts queries by. Make one client per page the shopper loads: once in the browser, and once per request on a server, with the shopper's User-Agent:

app/search.server.ts
import {  } from "@orbsearch/client";

export function (: ) {
    const  = ..("user-agent");

    return ({ : "http://127.0.0.1:7800", ...( && {  }) });
}

The user agent tells a crawler from a shopper, which the server's own would hide. The page view lives in memory only: pass the server's to the browser's client as pageView, and a page load counts once. traffic: "test" or "bench" marks searches the merchant's reports leave out.

Reference: page_view · traffic

Errors

A call the Query API refuses throws a SearchError, whose problem says why in words a person can read:

app/search.ts
import { ,  } from "@orbsearch/client";

const  = ({ : "http://127.0.0.1:7800" });

try {
    await .("sofa", { : "cheapest" });
} catch () {
    if (!( instanceof )) throw ;
    .(.., ..);
}
  • 400 means the request cannot be answered as sent, such as the unknown sort above, and problem.violations names each wrong field.
  • 503 means nothing is searchable yet or the engine is unreachable. Offer the shopper the same page again.

Reference: Problems

Types from the contract

Every request, answer, release file and catalog entity is a type generated from the contract, such as SearchResponse, FacetResult or PageResponse. Hover one to read its fields:

app/hits.ts
import type {  } from "@orbsearch/client";

export function (: ): string[] {
    return (. ?? []).(() => ..(() => .));
}

The contract's values come as constants, such as SEARCH_PATH, RELEVANCE_SORT and LATENCY_BUDGETS. Mind one name: Product from the client is a product page's product, Product from @orbsearch/react a tile's.

Reference: Query API

A listing's URL

A search, category or brand page keeps its whole state in its URL, so a link, the back button and a crawler see what the shopper sees. listingHref writes it:

app/links.ts
import {  } from "@orbsearch/client";

("/suche", {
    : "sofa",
    : { : ["grey", "green"], : { : 100, : 300 }, : true },
    : "price_asc",
    : 2,
});
// "/suche?q=sofa&color=green&color=grey&on_sale=1&price.gte=100&price.lte=300&sort=price_asc&page=2"

Fields stand in alphabetical order and values sorted, so one choice has one URL. Leave page out after a change of filters or sort, and the page starts at 1. Three helpers read a listing back:

  • listingOf(page) reads it from resolve's answer, since the merchant may rename parameters.
  • keptParams(listing) holds what a form that changes filters or sort keeps as hidden fields, such as q.
  • filtersOf(url, facets) reads filters for facets you hold yourself. It needs the facets, since seats=1 is a value where on_sale=1 is a toggle.

The helpers write each parameter by its code, as a release without parameters in routes.json does; URLs and redirects says what renamed parameters change.

Reference: routes.json

Next

On this page