← Files WixARCHIVED FILE

skills/wix-headless-templates/shared/capabilities/site-search/app/wix/site-search/search.ts

3.93 KB · Oct 10, 2026 · 12:03 UTC

↓ Download file

// Site Search reads (@wix/search `siteSearch`, the federated service) over the SDK — the only file on
// this transport that sees raw search documents. The rules, bodies and mappers live in ./search-core
// (shared with the REST twin in rest/search.ts); this file is the transport only. Every call runs as
// the visitor: both search surfaces answer an anonymous visitor token (observed live; the typings say
// APP and the docs show an admin client — a closed door here means an Astro API route with
// auth.elevate(wixSiteSearch.search), per CUSTOM_OPERATIONS.md). Copy as-is; extend by adding
// functions (`related`, `trending`, `federatedAutocomplete` exist on the same module).
// docs: https://dev.wix.com/docs/api-reference/business-management/site-search/wix-site-search/search.md
import { siteSearch } from "@wix/search";
import { wixModule } from "../sdk";
import { imgSrc } from "../media";
import { siteSearchPlan } from "./config.generated";
import {
  GROUP_LIMIT,
  MIN_SUGGEST_CHARS,
  MAX_SUGGEST_QUERY,
  SUGGEST_LIMIT,
  clampQuery,
  federatedBody,
  productsOf,
  resolveConfig,
  searchBody,
  storeFrontById,
  suggestBody,
  toPage,
  toSearchAll,
  toSuggestGroups,
  type Raw,
} from "./search-core";
import type { SearchAllResult, SearchOptions, SearchPage, SiteSearchConfig, SuggestGroup } from "./types";

const search = wixModule(siteSearch);

/** The plan's siteSearch entry resolved — types in order, routes, labels. */
export const config: SiteSearchConfig = resolveConfig(siteSearchPlan);

// The store-front pass is a second request that only adds product slugs and media; when it fails the
// products render unlinked instead of failing the search.
async function storeFrontPass(run: () => Promise<Raw>): Promise<Raw | null> {
  try {
    return await run();
  } catch {
    return null;
  }
}

/**
 * Every configured type at once, `limit` hits per type — the grouped results page.
 * Products are fetched twice (default format for title and highlights, store-front for the slug,
 * see search-core) and joined on id. Empty `q` → no request, `indexed: null`.
 */
export async function searchAll(q: string, { limit = GROUP_LIMIT }: { limit?: number } = {}): Promise<SearchAllResult> {
  const query = clampQuery(q);
  if (!query) return { groups: [], indexed: null };
  const wantsProducts = config.types.includes("products");
  const [res, sf] = await Promise.all([
    search.federatedSearch(federatedBody(query, config, { limit }) as siteSearch.FederatedSearchOptions),
    wantsProducts ? storeFrontPass(() => search.federatedSearch(federatedBody(query, config, { limit, types: ["products"], storeFront: true }) as siteSearch.FederatedSearchOptions)) : null,
  ]);
  return toSearchAll(res.results as Raw[], config, imgSrc, storeFrontById(productsOf(sf?.results as Raw[] | undefined)));
}

/** One type with paging, ordering, filter and facets — the "show all" view and its pages. */
export async function searchType(o: SearchOptions): Promise<SearchPage> {
  const query = clampQuery(o.q);
  if (!query) return toPage(null, o, config, imgSrc);
  const [res, sf] = await Promise.all([
    search.search(searchBody({ ...o, q: query }, config) as siteSearch.SearchOptions),
    o.type === "products" ? storeFrontPass(() => search.search(searchBody({ ...o, q: query }, config, { storeFront: true }) as siteSearch.SearchOptions)) : null,
  ]);
  return toPage(res as Raw, { ...o, q: query }, config, imgSrc, storeFrontById(sf?.documents as Raw[] | undefined));
}

/** Suggestions for a partial phrase (3 to 50 chars; fewer → [] without a request), `limit` per type. */
export async function suggest(q: string, { limit = SUGGEST_LIMIT }: { limit?: number } = {}): Promise<SuggestGroup[]> {
  const query = clampQuery(q, MAX_SUGGEST_QUERY);
  if (query.length < MIN_SUGGEST_CHARS) return [];
  const res = await search.federatedSuggest(suggestBody(query, config, limit) as siteSearch.FederatedSuggestOptions);
  return toSuggestGroups(res.results as Raw[], config, imgSrc);
}

SHA-256: 396c9772567640e5f9de5b2de76d075ec388e413491ad434e45268c8ad0db96c