AI-powered search is everywhere, but if you use an ORM, you've probably hit this wall: the moment you need vector similarity, you're forced to use raw SQL. Hand-written distance expressions, and dialect-specific quirks — all outside your type-safe query API.
UQL 0.3 changes that. Semantic search can now be used as a first-class citizen, all within a unified API.
What It Looks Like
const results = await querier.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryEmbedding, $distance: 'cosine' } },
$limit: 10,
});
UQL generates the right SQL for your database:
SELECT "id", "title" FROM "Article"
ORDER BY "embedding" <=> $1::vector
LIMIT 10
SELECT `id`, `title` FROM `Article`
ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?)
LIMIT 10
SELECT `id`, `title` FROM `Article`
ORDER BY vec_distance_cosine(`embedding`, ?)
LIMIT 10
No raw SQL. No dialect checks. Same query everywhere.
For MongoDB, UQL translates into an Atlas $vectorSearch pipeline — same API, zero config:
[
{ "$vectorSearch": { "index": "embedding_index", "path": "embedding", "queryVector": ["..."], "numCandidates": 100, "limit": 10 } }
]
Entity Setup
Define your vector field and index — UQL handles schema generation, extension creation, and index building:
import { Entity, Id, Field, Index } from 'uql-orm';
@Entity()
@Index(['embedding'], { type: 'hnsw', distance: 'cosine', m: 16, efConstruction: 64 })
export class Article {
@Id() id?: number;
@Field() title?: string;
@Field({ type: 'vector', dimensions: 1536 })
embedding?: number[];
}
For Postgres, UQL automatically emits CREATE EXTENSION IF NOT EXISTS vector. MariaDB and SQLite just work — no extensions needed.
Key Features
Distance Projection
Project the computed distance into your results with $project — no duplicate computation:
import type { WithDistance } from 'uql-orm';
const results = await querier.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine', $project: 'similarity' } },
$limit: 10,
}) as WithDistance<Article, 'similarity'>[];
results[0].similarity; // autocomplete works ✓
WithDistance<E, K> adds a typed distance property to each result — your IDE autocompletes it, and typos are caught at compile time.
5 Distance Metrics
| Metric | Postgres | MariaDB | SQLite | MongoDB Atlas |
|---|---|---|---|---|
cosine |
<=> |
✅ | ✅ | ✅ (index-defined) |
l2 |
<-> |
✅ | ✅ | ✅ (index-defined) |
inner |
<#> |
— | — | ✅ (index-defined) |
l1 |
<+> |
— | — | — |
hamming |
<~> |
— | ✅ | — |
3 Vector Types
| Type | Storage | Use Case |
|---|---|---|
'vector' |
32-bit float | Standard embeddings (OpenAI, etc.) |
'halfvec' |
16-bit float | 50% storage savings, near-identical accuracy |
'sparsevec' |
Sparse | SPLADE, BM25-style sparse retrieval |
halfvec and sparsevec are Postgres-only. MariaDB and SQLite transparently map them to their native VECTOR type — your entities work everywhere.
Vector Indexes
| Type | Postgres | MariaDB | MongoDB Atlas |
|---|---|---|---|
| HNSW | ✅ with m, efConstruction
|
❌ | ❌ |
| IVFFlat | ✅ with lists
|
❌ | ❌ |
| Native | — | ✅ VECTOR INDEX
|
✅ $vectorSearch
|
Why $sort?
Vector similarity search is fundamentally sorting by distance. UQL reuses the existing $sort API, which composes naturally with $where, $select, $limit, and regular sort fields:
const results = await querier.findMany(Article, {
$where: { category: 'science' },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine' }, title: 'asc' },
$limit: 10,
});
No new concepts to learn. The query API you already know now handles AI search.
Get Started
npm i uql-orm
We'd love to hear how you're using vector search in your projects — join the Discord and let us know!
Top comments (0)