# Browser SDK

> @findlane/browser searches an index from the browser with its public ID. It has no dependencies and is about 1 KB.

## Install

```sh
npm install @findlane/browser
```

## Create a client

```ts
import { SearchBrowserClient } from "@findlane/browser"

const client = new SearchBrowserClient({ publicId: "pub_your_public_id" })
```

| Option     |                                                                                          |
| ---------- | ---------------------------------------------------------------------------------------- |
| `publicId` | The index's public ID, from its **Public access** page.                                  |
| `baseUrl`  | The API to call. Leave it out to use Findlane's hosted API.                              |
| `fetch`    | A `fetch` implementation to use instead of the global one, for tests or instrumentation. |

Browser search works from the index's allowed origins; see [Security](/docs/security.md#public-ids-and-allowed-origins).

## search(request, options)

```ts
type Product = {
  objectID: string
  title: string
  brand: { name: string }
  price: number
  image: string
}

const results = await client.search<Product>(
  { q: "trail shoe", facets: ["brand.name"], limit: 24, highlight: true },
  { signal: controller.signal }
)
```

The type parameter types each hit's `record`. `options.signal` cancels the request.

### Request

| Field                  | Type                               |                                                                                                                     |
| ---------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `q`                    | `string`                           | The query, up to 200 characters.                                                                                    |
| `filters`              | `object`                           | Field values to keep: a value, `{ in: [...] }`, or `{ gte, lte }` for numbers. See [Filters](/docs/search.md#filters). |
| `facets`               | `string[]`                         | Facet fields to count values for, up to eight.                                                                      |
| `sort`                 | `string`                           | A sort ID from the index's settings, or `"relevance"`.                                                              |
| `limit`                | `number`                           | Hits to return, 1 to 50. Default 20.                                                                                |
| `offset`               | `number`                           | Hits to skip, up to 1,000.                                                                                          |
| `countMode`            | `"page"` or `"exact"`              | `"exact"` counts every match in `total`.                                                                            |
| `facetMode`            | `"page"` or `"exact"`              | `"exact"` counts facet values across every match.                                                                   |
| `highlight`            | `boolean` or `{ preTag, postTag }` | Mark matched words in `highlights`.                                                                                 |
| `attributesToRetrieve` | `string[]`                         | Return only these fields.                                                                                           |

### Response

| Field           | Type                                     |                                                                                     |
| --------------- | ---------------------------------------- | ----------------------------------------------------------------------------------- |
| `hits`          | `{ id, score, record, highlights? }[]`   | The matching records. `score` is `null` without a query or when sorting by a field. |
| `total`         | `number`                                 | Matches: a lower bound when `totalRelation` is `"gte"`.                             |
| `totalRelation` | `"eq"` or `"gte"`                        | Whether `total` is exact.                                                           |
| `hasMore`       | `boolean`                                | Whether there are hits after this page.                                             |
| `facets`        | `Record<string, Record<string, number>>` | Counts per value of each requested facet.                                           |
| `sort`          | `string`                                 | The sort used.                                                                      |
| `tookMs`        | `number`                                 | Time the search took on the server.                                                 |

## Errors

A failed request throws a `FindlaneError` with the HTTP `status` and the API's `message`:

```ts
import { FindlaneError } from "@findlane/browser"

try {
  await client.search({ q })
} catch (error) {
  if (error instanceof FindlaneError && error.status === 429)
    showMessage("Too many searches, try again shortly")
  else throw error
}
```

| Status | Meaning                                                                          |
| ------ | -------------------------------------------------------------------------------- |
| `400`  | The request isn't valid, such as an unknown facet or sort. The message says why. |
| `402`  | The workspace has used the searches its plan includes this month.                |
| `403`  | This origin isn't allowed, or the public ID doesn't exist.                       |
| `429`  | Too many searches: 240 a minute per visitor, or 6,000 per index.                 |

A cancelled request rejects with the `AbortError` from `fetch`.

## Without a bundler

The package is a standard ES module, so a page can load it from a CDN that serves npm packages:

```html
<script type="module">
  import { SearchBrowserClient } from "https://esm.sh/@findlane/browser@0"

  const client = new SearchBrowserClient({ publicId: "pub_your_public_id" })
  const { hits } = await client.search({ q: "jacket" })
  console.log(hits)
</script>
```
