Last Updated: 2026-01-19
Browser-First, WASM-Powered
- No server required - everything runs client-side
- WASM for performance (DuckDB, Lezer parser)
- Progressive enhancement (works offline, file persistence)
CSV File
↓
[User imports via File System Access API]
↓
DuckDB WASM (in browser)
↓
[User writes KQL query in CodeMirror]
↓
Lezer Parser (kql-lezer) → AST
↓
Translator (kql-to-duckdb) → DuckDB SQL
↓
DuckDB WASM executes query
↓
Results → TanStack Table (virtualized)
Technology: Lezer grammar → LRParser Purpose: Real-time syntax highlighting + AST generation
src/kql.grammar (Lezer grammar definition)
↓
@lezer/generator
↓
src/parser.ts (generated LRParser)
↓
src/parser/cst-to-ast/ (CST → AST mapping)
↓
@fossiq/kql-ast types
Key Files:
src/kql.grammar- Grammar definition (hand-written)src/parser.ts- Generated parser (DO NOT EDIT)src/parser/cst-to-ast/index.ts- Main CST→AST mappersrc/index.ts- Public API (parseKQL())
Technology: AST visitor pattern Strategy: CTE-based pipeline generation
// Input KQL
Table | where X > 10 | project Y, Z
// Output SQL
WITH cte_0 AS (
SELECT * FROM Table WHERE X > 10
),
cte_1 AS (
SELECT Y, Z FROM cte_0
)
SELECT * FROM cte_1Key Files:
src/translator.ts- Main translation logicsrc/types.ts- Internal types (imports from kql-lezer)src/index.ts- Public API (translateKQL())
Translation Steps:
- Parse operators from AST
- Generate SQL for each operator
- Chain via CTEs
- Return final SELECT
Purpose: Language-agnostic AST types shared between parser implementations Status: Types defined, not yet fully integrated
Design Principles:
- No parser-specific dependencies (no Lezer, no tree-sitter)
- Position tracking on all nodes
- Discriminated unions for type safety
Stack: SolidJS + Vite + PicoCSS + CodeMirror 6 + DuckDB WASM
┌─────────────────────────────────────────┐
│ Header (logo, title) │
├──────────────────┬──────────────────────┤
│ │ │
│ Editor │ Sidebar │
│ (CodeMirror) │ - Add Data button │
│ - KQL query │ - File list │
│ - Lezer syntax │ │
│ highlighting │ │
│ │ │
├──────────────────┴──────────────────────┤
│ Results Table (TanStack + Virtual) │
│ - Column headers │
│ - Virtualized rows │
│ - Horizontal scroll │
└─────────────────────────────────────────┘
Key Components:
src/App.tsx- Main layoutsrc/components/Editor.tsx- CodeMirror integrationsrc/components/ResultsTable.tsx- Virtualized resultssrc/contexts/SchemaContext.tsx- DuckDB connection management
State Management:
- File handles → IndexedDB (persistence)
- Query text → localStorage
- Theme preference → localStorage + DOM classes
- DuckDB connection → SolidJS context
Purpose: Generate .grammar text files from TypeScript objects
Use Case: Type-safe grammar development
// Input: TypeScript
const grammar = {
name: "KQL",
rules: {
Expression: ["BinaryExpression", "Literal"],
// ...
}
}
// Output: .grammar file
Expression { BinaryExpression | Literal }Monorepo: Bun workspaces + Turborepo Versioning: Changesets (fixed mode - all packages version together) CI/CD: GitHub Actions
Build Order (Turborepo manages this):
kql-ast(types only)lezer-grammar-generator(tooling)kql-lezer(depends on kql-ast)kql-to-duckdb(depends on kql-lezer via AST types)ui(depends on kql-lezer, kql-to-duckdb)
Rationale:
- No WASM binary needed for parser (Lezer is pure JS)
- Better CodeMirror integration (Lezer is from same team)
- Incremental parsing for editor performance
- Easier to distribute (no native bindings)
Trade-off: Less mature ecosystem than tree-sitter
Rationale:
- Clean separation between operators
- Easy to debug (each CTE is one KQL operator)
- Composable (operators chain naturally)
Trade-off: Verbose SQL output (vs inline WHERE/JOIN)
Rationale:
- Better analytics performance
- Native array/JSON support
- INTERVAL/DATE types match KQL semantics
- Growing WASM support
Trade-off: Larger WASM binary (~2MB vs ~1MB for SQLite)
Rationale:
- True reactivity (no VDOM diffing)
- Smaller bundle size
- Better performance for data-heavy tables
Trade-off: Smaller ecosystem, different mental model
Parser: Incremental, sub-millisecond for typical queries Translator: Linear in AST size, negligible overhead DuckDB: Columnar storage, SIMD operations, parallel execution Table Rendering: Virtualization (only visible rows in DOM)
Sandboxed: All execution in browser WASM sandbox File Access: Explicit user consent via File System Access API No Network: Intentionally offline-first (no data exfiltration)
Required:
- File System Access API (Chrome 86+, Edge 86+)
- WASM (all modern browsers)
- ES2020+ (modules, optional chaining, nullish coalescing)
Graceful Degradation:
- File handles lost on page reload if API unavailable
- Falls back to localStorage for query persistence