Skip to content

Tech Stack

Karthik P edited this page Oct 16, 2026 · 8 revisions

Tech Stack

Blinkql is a static, client-only single-page application. Everything below runs in the user's browser; the only infrastructure is static hosting.

At a glance

flowchart LR
  UI["React + TypeScript UI<br/>shadcn/ui · Tailwind"] -->|SQL| W["DuckDB-wasm<br/>(Web Worker)"]
  W -->|Arrow record batches| UI
  W <--> S[("OPFS / IndexedDB")]
  W <-->|HTTP range requests| R["Remote Parquet / CSV"]
Loading

Application

Layer Choice Why
Language TypeScript (strict mode, noUncheckedIndexedAccess) Type safety across the UI and the engine boundary
UI framework React Mature ecosystem, widely known by contributors. Keeping React fast with large data is part of the engineering challenge; see ADR-002
Build tool & dev server Vite Fast builds, first-class Web Worker and WebAssembly handling, static output
Query engine DuckDB-wasm (@duckdb/duckdb-wasm) Columnar analytical SQL in the browser; native CSV, Parquet and JSON readers; reads remote files with HTTP range requests
In-memory data format Apache Arrow JS (apache-arrow) Columnar results that cross the worker boundary without row-by-row conversion
Threading Web Workers: DuckDB-wasm's worker-based async API, plus Comlink for any additional Blinkql workers Query execution and heavy processing stay off the main thread
SQL editor CodeMirror 6 with @codemirror/lang-sql Lightweight and modular, with schema-aware autocomplete; see ADR-005
Results grid TanStack Virtual, evolving to a custom canvas-rendered grid Only visible rows are rendered; the canvas grid is a planned performance milestone
Charts Apache ECharts (canvas renderer), plus uPlot for dense time series Canvas rendering scales to large point counts where SVG struggles
UI components shadcn/ui (Base UI primitives, Mira style) + Tailwind CSS v4 Accessible primitives and a consistent design, with components owned in the repo. Base UI has been shadcn/ui's default since July 2026 and is its recommendation for new projects; see ADR-009 and Design Language
Icons lucide-react Consistent, tree-shakeable icon set
Typography Atkinson Hyperlegible Next and Mono, self-hosted via Fontsource Built to tell similar characters apart (0/O, 1/l/I) in codes and IDs; no runtime font CDN. See Design Language
UI state Zustand Minimal, selector-based subscriptions. Large result data is deliberately kept outside React state; see ADR-004
Local persistence OPFS for data files, IndexedDB for saved queries, history and settings Fast, origin-private storage that survives reloads
Offline (planned) Service worker via vite-plugin-pwa Caches the app shell and engine for instant repeat loads and offline use

Quality, testing & performance tooling

Purpose Tool
Package manager pnpm
Linting & formatting Biome, one tool for both, including the React hooks rules; see ADR-010
Unit & integration tests Vitest + React Testing Library
End-to-end tests Playwright (Chromium, Firefox, WebKit)
Accessibility checks axe-core via @axe-core/playwright
Benchmarks Custom Playwright-driven harness using the User Timing API (performance.mark / measure), plus tinybench for micro-benchmarks
Performance audits Lighthouse CI, React Profiler, Chrome DevTools performance traces
Bundle budgets size-limit, with rollup-plugin-visualizer for analysis
Commit conventions Conventional Commits, enforced with commitlint: locally through a Lefthook commit-msg hook, and on PR titles in CI
Releases & changelog release-please
Dependency updates Dependabot

Delivery

Stage Setup
Continuous integration GitHub Actions: typecheck, lint, unit tests, build, end-to-end tests, bundle-size check and a benchmark smoke test on every pull request
Preview environments A Vercel preview deployment for every pull request
Production Vercel static hosting at blinkql.vercel.app, deployed from main
Engine assets DuckDB-wasm bundles and the DuckDB extensions Blinkql uses (parquet, json) are self-hosted (same origin, version-pinned, long-term cached; extensions are checksum-verified at build time) rather than loaded from a CDN; see ADR-008

Response headers are set in vercel.json, the single source for production, previews and local development (Vite's dev and preview servers read the same file):

Header Value Why
Cross-Origin-Opener-Policy same-origin With COEP, makes the page cross-origin isolated: required for SharedArrayBuffer, multithreading and precise memory measurement
Cross-Origin-Embedder-Policy require-corp credentialless would be more lenient, but Safari doesn't support it
Cross-Origin-Resource-Policy same-origin Other sites can't embed Blinkql's files
Content-Security-Policy Scripts, styles, fonts, workers and connections from Blinkql's own origin only, plus 'wasm-unsafe-eval' for WebAssembly; no plugins; can't be framed See Privacy & Security. Loosen it deliberately, in the PR that needs it (for example connect-src for remote queries)
X-Content-Type-Options nosniff Browsers never guess a file's type
Referrer-Policy strict-origin-when-cross-origin Remote hosts see only the origin, never the full URL
Permissions-Policy Camera, microphone, location, payment, USB and Topics disabled Blinkql uses none of them
Cache-Control on /assets/* public, max-age=31536000, immutable Vite puts a content hash in these filenames, so they never change

The CSP is left out of pnpm dev only, because Vite's hot reload needs inline scripts. End-to-end tests run against the production build with every header, and each deploy fails if a header is missing (scripts/verify-headers.ts).

Deliberately not in the stack

  • No server framework (Next.js, a Node API, etc.). There's nothing to render or serve dynamically; see ADR-001 and ADR-002.
  • No Redux. UI state is small, and large data doesn't live in a store.
  • No Monaco Editor. It adds several megabytes and hurts cold-start time.
  • No SVG charting for large series. SVG creates one DOM node per point and doesn't scale.

Clone this wiki locally