# Vector search on documents
Source: https://sovaria.in/docs/document-vector-search

Find the documents nearest to a vector (Firestore findNearest): cosine, euclidean or dot product, with filters first and an HNSW index for large collections.

Store an embedding in any document field as an array of numbers, then ask for the documents whose vector is closest to yours — Firestore's `findNearest`. It runs on pgvector in your project's Postgres, as the caller, so collection rules and row policies decide what comes back. For tables, see [vector search on tables](https://sovaria.in/docs/vector-search); for documents with text and automatic chunking, [knowledge bases](https://sovaria.in/docs/knowledge-bases).

(Diagram: How a nearest-neighbour query runs)

JavaScript:
```js
await sov.doc('items/a').set({ kind: 'fruit', embedding: [0.12, 0.88, 0.05] })

const near = await sov.collection('items')
  .where('kind', '==', 'fruit')                         // pre-filter (optional)
  .findNearest({ vectorField: 'embedding', queryVector: [0.1, 0.9, 0.0], limit: 5,
                 distanceMeasure: 'COSINE', distanceThreshold: 0.3 })
  .get()
near.docs.forEach((d) => console.log(d.id, d.distance))

// Firestore code works unchanged
import { collection, query, where, findNearest, vector, getDocs } from '@sovaria/sdk/firestore'
await getDocs(findNearest(query(collection(db, 'items'), where('kind', '==', 'fruit')),
  { vectorField: 'embedding', queryVector: vector([0.1, 0.9, 0.0]), limit: 5, distanceMeasure: 'EUCLIDEAN' }))
```

Python:
```python
near = await (sov.collection("items").where("kind", "==", "fruit")
              .find_nearest("embedding", [0.1, 0.9, 0.0], limit=5, distance_measure="cosine").get())
for doc in near.docs:
    print(doc.id, doc.distance)
```

Dart:
```dart
final near = await sov.collections.collection('items')
    .findNearest('embedding', [0.1, 0.9, 0.0], limit: 5, where: [['kind', '==', 'fruit']]);
```

Swift:
```swift
let near = try await sov.collections.collection("items")
    .findNearest("embedding", queryVector: [0.1, 0.9, 0.0], limit: 5, where: [["kind", "==", "fruit"]])
```

Kotlin:
```kotlin
val near = sov.collections.collection("items")
    .findNearest("embedding", listOf(0.1, 0.9, 0.0), limit = 5, where = listOf(listOf("kind", "==", "fruit")))
```

HTTP:
```bash
curl -s -X POST $URL/api/v1/data/doc/_nearest/items \
  -d '{"field":"embedding","vector":[0.1,0.9,0.0],"limit":5,"distance":"cosine",
       "where":[["kind","==","fruit"]],"distance_threshold":0.3,"distance_field":"score"}' …
# → {"documents":[{"id":"a","path":"items/a","data":{…,"score":0.004},"distance":0.004}…],
#    "distance":"cosine","dimensions":3}
```

CLI:
```bash
sov collections nearest items --field embedding --vector '[0.1,0.9,0.0]' --limit 5 --where '[["kind","==","fruit"]]'
```

## Options

| Option | What it does |
|---|---|
| `field` / `vectorField` | The field holding the vector: an array of numbers. Documents whose field isn't an array of exactly the query's length are skipped (never an error). |
| `vector` / `queryVector` | The query vector, up to 2,000 numbers. |
| `limit` | How many nearest documents to return, up to your plan's `docs.vector_max_neighbors`. |
| `distance` / `distanceMeasure` | `cosine` (default), `euclidean` or `dot_product`. Results come back closest first; for `dot_product`, `distance` is the inner product and larger is closer. |
| `where` | Filters applied first, the same as in a query (OR groups too). |
| `distance_threshold` | `cosine` / `euclidean`: at most this distance. `dot_product`: at least this product. |
| `distance_field` / `distanceResultField` | Also write the distance into each result's `data` under this field. |
| `collection_group` | Search every collection with this id at any depth (see [collection groups](https://sovaria.in/docs/document-queries#collection-groups)). |

## Vector indexes

Without an index the search is exact: it compares every matching document. For large collections, create an **HNSW** vector index for the field, its dimensions and the distance you query with; queries then use it automatically (with filters or row policies, pgvector keeps scanning until it finds `limit` visible results). It builds in the background without blocking writes and counts toward `docs.max_indexes`.

HTTP:
```bash
curl -s -X POST $URL/api/v1/data/doc/_indexes \
  -d '{"collection":"items","vector":{"field":"embedding","dimensions":768,"distance":"cosine"}}' …
```

Python:
```python
# Sovariq / tools: create_document_index(collection="items", vector={"field": "embedding", "dimensions": 768, "distance": "cosine"})
```

- pgvector is available on every plan. If it isn't turned on yet, the first search by a project team member enables it; app users get `409 VECTOR_UNAVAILABLE` until then.
- A search slower than your plan's statement timeout returns `504 DEADLINE_EXCEEDED` with the vector index that would help.
- Need embeddings for text? Use your own model, or [the project's embedding model](https://sovaria.in/docs/vector-search) through the vectors API.

## Plan limits

|  | Free | Pro | Ultra | Team | Enterprise |
|---|---|---|---|---|---|
| Nearest neighbours one document vector search returns | 100 | 500 | 1,000 | 1,000 | 1,000 |
| Document indexes per project | 10 | 100 | 300 | 1,000 | 5,000 |
| Default SQL statement timeout (ms) | 3 s | 8 s | 15 s | 30 s | 60 s |

Asking for more neighbours than your plan allows returns `403 PLAN_LIMIT_REACHED`.
