uqa-js: The Browser Database That Turns SQL, Graphs, Search, and Vectors into One Algebra
A TypeScript port of Unified Query Algebra that uses posting lists, functors, and calibrated fusion to make hybrid queries feel like one native system.
- uqa-js is interesting because it does not merely add more query modes, it tries to make them mathematically interchangeable.
- The universal posting list is the project’s real primitive, because it gives SQL, graph, text, and vector results a shared form before fusion happens.
- Hybrid search becomes more credible here because scoring is calibrated instead of guessed, so incompatible signals are aligned before they are ranked.
- The browser is not the gimmick. The point is local orchestration over multiple specialized backends through one algebraic engine.
Most hybrid search systems start by bolting features together. uqa-js starts by asking a sharper question: what if SQL rows, graph traversals, full-text hits, and vector neighbors were all just different views of the same thing? That is the project’s real trick, and it is why the code reads less like a mashup and more like a formal port of a query engine.
One Posting List to Rule Them All
The core abstraction is the PostingList: a sorted list of (DocId, Payload). In a classic search engine, that is enough to represent inverted indexes. In uqa-js, it becomes the universal currency for query results. Once everything is in that shape, set operations like union, intersection, and difference can combine very different retrieval modes without needing a new rulebook for each one.
That separation matters. The project does not treat scoring as a cosmetic afterthought. It treats scoring as a second pass on top of a shared algebra. That is the difference between a clever integration layer and a system that can actually reason about mixed query semantics.
Why Hybrid Search Gets Hard So Fast
Hybrid retrieval sounds simple until you try to compare scores that were never meant to live on the same scale. BM25 is not vector similarity. A graph match is not a keyword hit. A relational filter is not a relevance score at all. Most systems solve that mismatch with hand-tuned weights and hope the ranking behaves.
| Problem | Siloed stack | uqa-js |
|---|---|---|
| Query model | Each backend speaks its own language | Different models collapse into posting lists first |
| Score handling | Heuristic score blending | Calibrated fusion with probabilistic meaning |
| Integration | Glue code between engines | One algebraic execution layer |
| Deployment | Multiple services and adapters | Browser-native orchestration with pushdown where possible |
| Cross-paradigm joins | Ad hoc and brittle | Functor-based casting between models |
That is the pressure point uqa-js is aiming at. It wants to turn mixed retrieval from a bag of heuristics into something closer to a mathematically legible pipeline.
How uqa-js Collapses the Models
The repository is organized like an engine, not a toolkit. src/core/ holds the mathematical base, src/operators/ implements the algebra, src/sql/ and src/graph/ parse different languages, src/storage/ handles indexes, and src/fdw/ connects to external systems such as DuckDB and Arrow. That layout says the quiet part out loud: the job is coordination.
// Simplified mental model
const sqlHits = await engine.sql(queryA)
const graphHits = await engine.graph(queryB)
const textHits = await engine.search(queryC)
const vectorHits = await engine.knn(queryD)
const unified = PostingList.union(
sqlHits,
graphHits,
textHits,
vectorHits
)
const ranked = fusion.deepMerge(unified)
The important move is the normalization step. If every source can be translated into a posting list, then operators do not have to care where the rows came from. They only need to know how to combine them.
The Functor Trick
This is where the project stops being merely multi-model and starts being formally interesting. The repository includes functor machinery such as GraphToRelationalFunctor and TextToVectorFunctor. In plain language, those are casting rules. They let one model be re-expressed in another without pretending the original structure never existed.
That matters for cross-paradigm joins. A graph relationship can become a relational row. Text can be lifted into vector space. Once the cast is explicit, the engine can do something most hybrids cannot: compose data models without collapsing them into a lowest-common-denominator schema.
Calibrating Relevance Instead of Guessing It
The scoring layer is where the engineering discipline shows up. A naive hybrid engine might add BM25 and vector similarity together and call it relevance. uqa-js instead leans on Bayesian calibration and likelihood ratios so that scores become comparable before they are fused. That is a more honest answer to the ranking problem.
| Approach | What it does | Trade-off |
|---|---|---|
| Raw score addition | Adds search signals directly | Fast, but mathematically sloppy |
| Weighted heuristics | Assigns manual importance to each signal | Flexible, but fragile and hard to tune |
| Bayesian calibration | Converts signals into comparable likelihoods | More work upfront, but much cleaner fusion |
That distinction is the difference between a demo and an engine. If you care about retrieval quality, the ranking math matters as much as the index structure.
DuckDB, Arrow, and the Manager of Managers Pattern
The foreign data wrapper layer makes the architecture pragmatic. Rather than re-implement every storage and execution backend, uqa-js can push work outward to specialized systems like DuckDB and Apache Arrow while keeping the hybrid logic local. It behaves like a manager of managers.
That is a strong pattern for local-first AI. Let the columnar engine stay columnar. Let the relational backend do relational work. Let the unified algebra own the awkward part, which is combining results across paradigms without losing semantics.
| Layer | Best at | What uqa-js keeps local |
|---|---|---|
| DuckDB / Arrow | Efficient relational and columnar execution | Hybrid fusion and cross-model composition |
| Search indexes | Fast text retrieval | Calibration and result composition |
| Vector indexes | Nearest-neighbor lookup | Score alignment and merging |
| uqa-js core | Unified query algebra | The orchestration logic |
What This Says About the Future of Local-First AI
The obvious pitch is browser-native execution. The deeper pitch is that browser-native execution becomes interesting only when the engine underneath is expressive enough to unify real query modes. If a local app needs semantic search, graph logic, and relational structure, shipping five disconnected systems is a tax. uqa-js is trying to reduce that tax without reducing the problem.
That is why this repo stands out. It is not just another hybrid search layer. It is an attempt to make multiple database paradigms mathematically interchangeable inside one browser-scale engine.





