AI & RAG
Semantic search inside your ORM
Section titled “Semantic search inside your ORM”With UQL, semantic search is part of your normal ORM workflow. You can store embeddings and query by meaning without adding a separate search stack.
- Multi-runtime support: Runs on Node.js, Bun, Deno, and the browser.
- Multi-database support: PostgreSQL (pgvector), CockroachDB, MariaDB, SQLite (via sqlite-vec), libSQL and Turso (built in), and MongoDB Atlas Vector Search.
- One query shape end-to-end: Use the same JSON query structure on backend and frontend.
End-to-end example
Section titled “End-to-end example”1. Define an entity with a vector field
Section titled “1. Define an entity with a vector field”import { Entity, Id, Field, Index } from 'uql-orm';
@Entity()@Index(['embedding'], { type: 'hnsw', distance: 'cosine', m: 16, efConstruction: 64 })export class Article { @Id({ type: Number }) id?: number; @Field({ type: String }) title?: string; @Field({ type: String }) category?: string;
@Field({ type: 'vector', dimensions: 1536 }) embedding?: number[];}2. Ingest content with embeddings
Section titled “2. Ingest content with embeddings”A single operation runs directly on the pool; pool.withQuerier() pins one connection for several.
import { pool } from './uql.config.js';import { Article } from './entities.js';
const embedding = await embed('What is UQL?'); // any embedding model
await pool.insertOne(Article, { title: 'What is UQL?', category: 'docs', embedding,});3. Query by meaning
Section titled “3. Query by meaning”import { pool } from './uql.config.js';import { Article } from './entities.js';import type { WithDistance } from 'uql-orm';
const queryEmbedding = await embed('TypeScript ORM with vector search'); // any embedding model
const results = (await pool.findMany(Article, { $where: { category: 'docs' }, $sort: { embedding: { $vector: queryEmbedding, $distance: 'cosine', $project: 'distance', }, }, $limit: 10,})) as WithDistance<Article, 'distance'>[];
for (const article of results) { console.log(article.title, article.distance);}$project adds the computed score to each row (here: distance), so your app can filter, rank, or inspect relevance. Annotate the result with the exported WithDistance<Article, 'distance'> helper to type the projected field.
The query shape stays the same across PostgreSQL, CockroachDB, MariaDB, SQLite, libSQL, Turso, and MongoDB Atlas.
Fullstack semantic search
Section titled “Fullstack semantic search”UQL queries are plain JSON, so the frontend can send the same query shape the backend uses:
// api.ts (Express)import express from 'express';import { querierMiddleware } from 'uql-orm/express';import { Article } from './entities.js';
const app = express();app.use('/api', querierMiddleware({ include: [Article] }));
// client.ts (Browser)import { HttpQuerier } from 'uql-orm/browser';import { Article } from './entities.js';
const queryEmbedding = await embed('semantic query from user input'); // any embedding modelconst http = new HttpQuerier('/api');
const { data: results } = await http.findMany(Article, { $where: { category: 'science' }, $sort: { embedding: { $vector: queryEmbedding } }, $limit: 5,});Production tips
Section titled “Production tips”Fetch a wider candidate set, then threshold
Section titled “Fetch a wider candidate set, then threshold”For RAG, fetch more candidates than you plan to use (e.g. $limit: 30), then keep low-signal results out of your context window:
import type { WithDistance } from 'uql-orm';
const candidates = (await pool.findMany(Article, { $where: { category: 'docs' }, $sort: { embedding: { $vector: queryEmbedding, $distance: 'cosine', $project: 'score', }, }, $limit: 30,})) as WithDistance<Article, 'score'>[];
const filtered = candidates.filter((row) => row.score <= 0.35);With cosine distance, lower values are better matches. Tune this threshold from real logs and user feedback. See the Semantic Search reference for how $where filtering and vector ranking combine in a single query on each database.