Skip to main content
OPEN SOURCE · SEARCH ENGINE · RUST · TYPESCRIPTTier 3 Deep Dive

json-query-lite: SQL-Shaped Search for Big JSON in Small Places

“Give JSON arrays a real query language and the UI stops being the database.”

My open-source weekend engine: lexer, parser, and BM25-ranked AST search in dependency-free TypeScript - with a Rust/Wasm sibling for heavy scans.

RUSTTYPESCRIPTSEARCH ENGINEOPEN SOURCE

Executive BriefSearch
Domain / ScopeSearch & Retrieval Systems
Architectural RoleAuthor & Maintainer
Timeline & InitiativeOpen source · independently maintained
Key Verified Invariant

MIT open source with a runnable 50,000-row demo and 50+ example queries

01 · The Constraint

What Made the Problem Difficult

I built this for myself first: every quoting and catalog experiment I ran needed the same thing - fast, expressive filtering over large JSON without standing up a search cluster. Transactional stores answer point queries well and analytical scans poorly; browsers answer neither well at 500K rows. A small dedicated engine fills the gap.

Core Platform Constraint

Systemic Friction

Quoting-style UIs need multi-attribute, nested-boolean filtering with ranking - the kind of query that is painful in SOQL-like point lookups and lethal as a full client-side scan. Dumping the dataset into the tab makes the UI the search engine, and the UI loses.

Boundary Invariants & Operational Limits:
  • !Expressive grammar: nested booleans, ranges, text operators, sorting with relevance
  • !Millisecond answers over tens of thousands of in-memory rows
  • !Zero dependencies and a bundle that fits LWC static resources
  • !Same semantics in Node, browsers, and the component - tested, not asserted
02 · The Architecture

What System Was Designed

A compact pipeline: lexer with wildcard and modifier support, recursive-descent parser into a typed AST (terms, predicates, AND/OR/NOT, numeric terms), then a two-pass engine - filter and collect term statistics first, BM25-score and sort second. Inverted field indexes serve equality fast paths; a query cache and an adaptive result cache absorb repeated shapes. The Rust sibling mirrors the grammar for bulk indexing behind the same contract.

03 · The Key Decision

Pivotal Architectural Choices

Key Architectural Decisions

ADR // 01DETERMINISTIC CHOICE

Dedicated engine vs. stretching the transactional store

Context:

Record stores optimize for consistency and point access, not ranked multi-attribute scans.

Alternatives Evaluated:
  • Wildcard queries against the record store
  • Third-party search embeds
  • Full client array scans
Chosen: Small purpose-built engines (Rust for bulk, TypeScript for edge) behind one grammar.

Each workload meets the runtime built for it, and the UI receives ranked IDs instead of raw tables.

ADR // 02DETERMINISTIC CHOICE

Ranked IDs plus viewport hydration vs. full-record streaming

Context:

Moving full records per keystroke wastes wire and DOM budgets together.

Alternatives Evaluated:
  • Streaming full records per filter change
  • Naive pagination with fat payloads
Chosen: Return ranked identifiers; hydrate only visible rows.

Payloads stay tiny and rendering stays constant-cost no matter the result size.

04 · The Engineering

What Was Actually Built

01

Wrote the lexer and parser by hand - arrays, modifiers, wildcards, and multi-column ORDER BY included - with a validate() entry for authoring feedback.

02

Implemented two-pass BM25 with dynamic 0–100 normalization so scores read sensibly at any corpus size.

03

Built inverted field indexes with explicit createIndex() for hot attributes plus automatic smart lookup.

04

Added query-AST caching (hundreds of entries) and adaptive result caching tuned for repeated interactive shapes.

05

Shipped ESM, CJS, types, and minified IIFE from one source; the demo dashboard runs 50,000 generated rows in-browser.

06

Mirrored the grammar in the Rust sibling (serde + wasm-bindgen surface) for bulk scans behind the identical query contract.

05 · The Trade-offs

Deliberate Architectural Compromises

Trade-offs & Mitigations

⚖External index freshness vs. live-store queries

Architectural Benefit:

Millisecond ranked answers with no platform search-limit consumption.

Associated Cost:

Indexes must follow source changes.

Mitigation Strategy:

In my studies, async change streams refresh indexes; staleness windows are explicit per use case.

⚖Two implementations vs. one language

Architectural Benefit:

Bulk throughput where it matters, edge agility where it counts.

Associated Cost:

Two toolchains behind one grammar.

Mitigation Strategy:

One shared grammar and contract tests asserting identical results across engines.

06 · The Result

Verified Outcomes

✓ VERIFIED RESULT

MIT open source with a runnable 50,000-row demo and 50+ example queries

✓ VERIFIED RESULT

Millisecond multi-attribute queries over large in-memory fixtures in the harness

✓ VERIFIED RESULT

BM25 relevance with 0–100 normalization across corpus sizes

✓ VERIFIED RESULT

Dependency-free IIFE that loads in browsers and LWC static resources

✓ VERIFIED RESULT

Rust sibling behind the same query contract for bulk indexing

07 · Architecture Blueprint

System Schematic & Data Flow

System Topology Blueprint
json-query-lite: Query Pipeline & Dual Runtimes
INTERACTIVE SCHEMATIC
NATIVE LIGHTNING WEB COMPONENT INTERFACECatalog Explorer & High-Volume Filter ControlsSingle-keystroke debounced query ingress · Zero browser-side table scanQUERY ROUTER & WORKLOAD DISTRIBUTORIntent Classification & Dual-Engine Query DispatchRUST COMPILED SEARCH ENGINEHigh-Throughput Inverted IndexMulti-attribute catalog index & spatial matchingZero garbage-collection pauses · Memory safetyTYPESCRIPT IN-MEMORY ENGINEDynamic Client & Edge EvaluationContextual filtering & session attribute rankingLow-overhead evaluation · Instant responsiveness
DECOUPLED RETRIEVALShields Salesforce CRM from expensive full-table scans and query timeout limits.
RUST PERFORMANCEUnmatched memory efficiency for multi-hundred-thousand record catalog indices.
TARGETED HYDRATIONOnly precise, ranked record pointers return to the LWC view layer.

One grammar, two engines: TypeScript for the edge, Rust for bulk scans, both behind identical contracts.

Text alternative for screen readers: Architecture flow: Query Text to Lexer & Parser via SQL-Shaped AST; AST to TypeScript Engine via In-Memory Filter + BM25; AST to Rust Engine via Bulk Inverted Index; Engines to Viewport Hydration via Ranked IDs Only

08 · Why It Matters

Architectural Conclusion

“A small query language beats a large workaround. JSON deserves better than Array.filter() and so do your users.”

Key Lessons for Platform Scale:
  • •Two-pass scoring (filter, then rank) keeps relevance honest at any corpus size.
  • •One grammar across two runtimes is a contract worth testing, not asserting.