Semantic Search: First-Class Vector Similarity in a Multi-Dialect ORM
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
Section titled “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::vectorLIMIT 10SELECT `id`, `title` FROM `Article`ORDER BY VEC_DISTANCE_COSINE(`embedding`, ?)LIMIT 10SELECT `id`, `title` FROM `Article`ORDER BY vec_distance_cosine(`embedding`, ?)LIMIT 10No 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
Section titled “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
Section titled “Key Features”Distance Projection
Section titled “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
Section titled “5 Distance Metrics”| Metric | Postgres | MariaDB | SQLite | MongoDB Atlas |
|---|---|---|---|---|
cosine | <=> | ✅ | ✅ | ✅ (index-defined) |
l2 | <-> | ✅ | ✅ | ✅ (index-defined) |
inner | <#> | — | — | ✅ (index-defined) |
l1 | <+> | — | — | — |
hamming | <~> | — | ✅ | — |
3 Vector Types
Section titled “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
Section titled “Vector Indexes”| Type | Postgres | MariaDB | MongoDB Atlas |
|---|---|---|---|
| HNSW | ✅ with m, efConstruction | ❌ | ❌ |
| IVFFlat | ✅ with lists | ❌ | ❌ |
| Native | — | ✅ VECTOR INDEX | ✅ $vectorSearch |
Why $sort?
Section titled “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
Section titled “Get Started”npm i uql-ormWe’d love to hear how you’re using vector search in your projects — join the Discord and let us know!