Skip to content

Semantic search: native vector similarity in a multi-dialect ORM

One $vector sort compiled to PostgreSQL, MariaDB, SQLite and MongoDB Atlas syntax side by sideOne $vector sort compiled to PostgreSQL, MariaDB, SQLite and MongoDB Atlas syntax side by side

Most ORMs stop short of vector similarity. Once you need it, you drop to raw SQL: hand-written distance expressions and dialect quirks, outside your type-safe query API.

UQL 0.3 adds semantic search to the regular query API, with automatic migration of HNSW and IVFFlat indexes.

You write
const queryVec = await embed('How do vector indexes work?');
const results = await pool.findMany(Article, {
$select: { id: true, title: true },
$sort: { embedding: { $vector: queryVec, $distance: 'cosine' } },
$limit: 10,
});

UQL generates each database’s SQL:

SELECT "id", "title" FROM "Article"
ORDER BY "embedding" <=> $1::vector
LIMIT 10

One query for every dialect, with no raw SQL or dialect checks in your code.

On MongoDB, the same query becomes an Atlas $vectorSearch pipeline. The search index itself is created in Atlas; the reference has the details.

[
{
"$vectorSearch": {
"index": "embedding_index",
"path": "embedding",
"queryVector": ["..."],
"numCandidates": 100,
"limit": 10
}
}
]

Declare the vector field and its index; UQL creates the column, the extension and the index:

import { f, Entity, Field } from 'uql-orm';
@Entity({
indexes: (article, index) => [
index([article.embedding], {
type: 'hnsw',
distance: 'cosine',
m: 16,
efConstruction: 64,
}),
],
})
export class Article {
@Field(f.number().autoIncrement()) id?: number;
@Field(f.string().nullable()) title?: string | null;
@Field(f.string().nullable()) category?: string | null;
@Field(f.vector(1536).nullable())
embedding?: number[] | null;
}

On Postgres, UQL emits CREATE EXTENSION IF NOT EXISTS vector. MariaDB has vectors built in; SQLite needs the sqlite-vec extension loaded.

$project returns the computed distance as a named field, without computing it twice, typed with the WithDistance helper (now inferred). Alongside it came four distance metrics (cosine, l2, inner, l1), three vector types (vector, halfvec, sparsevec), and HNSW, IVFFlat and native vector indexes, each where the engine has it. The reference lists which engine has which.

Similarity search is sorting by distance, so UQL reuses $sort, which combines with $where, $select, $limit and ordinary sort fields:

const results = await pool.findMany(Article, {
$where: { category: 'science' },
$sort: {
embedding: { $vector: queryVec, $distance: 'cosine' },
title: 'asc',
},
$limit: 10,
});
Terminal window
npm i uql-orm

If something is broken or missing, open an issue on GitHub.



UQL is a JSON-native TypeScript ORM for Node.js and Bun. Supports PostgreSQL, PGlite, MySQL, MariaDB, MSSQL, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.