based is a Bun workspace with four packages:
| Package | What it is |
|---|---|
core/ |
Bun server. Every bit of logic and every secret lives here: engine adapters, the agent, the language servers, storage, import/export. REST + NDJSON query streaming + SSE + a WebSocket LSP endpoint on 127.0.0.1:<port>, behind a per-launch bearer token. |
ui/ |
React 19 + Vite + Tailwind webview. Talks only to core. |
shell-tauri/ |
Tauri 2 native window. Deliberately thin and disposable — it spawns core as a child process and points a window at it. The only shell: dev and release run the same one. |
specs/ |
Requirements (specs/based/spec.md) and the tests that verify them. |
- Bun (a recent 1.x)
- The Rust toolchain (
rustup) — the shell is Rust, sobun run devbuilds it. The firstbun run devon a clean checkout compiles it and takes a few minutes; after that it is near-instant. If you are only working oncoreorui, thedev:core+dev:uibrowser loop below needs no Rust at all. - Windows 11 x64 for the shell and installer.
coreanduiare platform-agnostic enough to develop on, but secrets go through Windows Credential Manager and packaging is Windows-only. - For the installer: Inno Setup 6 (
winget install JRSoftware.InnoSetup) and .NET Framework 4.x (forcsc.exe, which builds the.sqlassociation stub).
bun installThree ways to run it, fastest feedback first.
bun run dev — the one you want. scripts/dev.ts starts core in watch mode
and Vite, waits for both to listen, then launches the Tauri window pointed at Vite
(BASED_DEV_URL=http://localhost:5183, which makes the shell skip spawning its own core). Full hot
reload inside the real window. Ctrl-C, or closing the window, tears all three down. Logs interleave
in one terminal. Rust changes are not hot-reloaded — restart to rebuild the shell.
bun run dev:core + bun run dev:ui — browser loop. Same core (port 7042, token dev) and
Vite (port 5183, proxying /api), but you iterate at http://localhost:5183 in a browser. The
client falls back to token dev when there's no URL hash, so there's no auth wiring to do.
bun run shell — production-like smoke test. The same Tauri shell with no BASED_DEV_URL, so
it spawns core as a real child process and serves the static ui/dist — the packaged topology, from
the checkout. No watch, no HMR — run bun run build:ui after UI changes or you'll be looking at a
stale bundle.
bun run dev # core + Vite + Tauri window, all with HMR
bun run dev:core # core alone on 127.0.0.1:7042
bun run dev:ui # Vite alone on 5183
bun run build:ui # -> ui/dist
bun run shell # Tauri window + core child over ui/dist
bun run typecheck # core, ui, shell-tauri
bun test # specsbun test # from the repo root; runs specs/Unit tests run anywhere. Integration tests need a real SQL Server and self-skip without one — they will not fail, they will report why they skipped. Point them at a database you control:
$env:BASED_TEST_SERVER = "your-server.database.windows.net"
$env:BASED_TEST_DB = "your_database"
az login
bun testThere is deliberately no default for those variables (specs/based/tests/_devDb.ts):
a real hostname must never be committed, and a silent fallback would make a green run ambiguous
about which database it actually hit. Auth is AzureCliCredential, so az login has to be current.
Most suites only need connect + read. The table-edit suites additionally probe for CREATE TABLE
permission and skip themselves if it isn't there.
The Snowflake suites need no live account — they assert connect-option construction, the bounded connect, and the driver-environment workaround, and run anywhere.
specs/based/spec.md is authoritative. Every requirement has an ID (BASED-*), a test category,
and either an executable test or a written verification procedure. Tests carry a
// Traces: BASED-XXX comment linking back. If you change specified behavior, update the spec in
the same change. See CONTRIBUTING.md.
.\scripts\package-win.ps1 # -> dist\based-<version>-Setup.exeFive steps: build the UI, bundle the core (shell-tauri/bundle-core.ts →
dist-core/{core,ui,bun}), build the Tauri shell (tauri build --no-bundle; Tauri's own NSIS
bundler is not used — Inno Setup is the installer, and keeping the electrobun-era Inno AppId
means installing over an old install upgrades it in place instead of registering a second
"based"), stage based-shell.exe + resources + icon, and run Inno Setup. The version comes from
shell-tauri/tauri.conf.json, which is the single source of
truth. Requires Inno Setup 6 (winget install JRSoftware.InnoSetup) and the Rust toolchain.
Actions tab -> "build (macOS)" -> Run workflow
macOS apps cannot be cross-compiled from Windows: the macOS SDK is licensed to Apple hardware, and
tauri build shells out to hdiutil and iconutil to make the bundle. So the build runs on a
GitHub-hosted macOS runner — real Mac hardware — via
.github/workflows/build-macos.yml, and no Mac is needed to
produce a .dmg. Standard runners are free for public repositories.
Same first three steps as Windows (build:ui → bundle-core.ts → tauri build), but it bundles a
.dmg rather than handing an unbundled exe to Inno Setup, and it checks the bundle layout first
(libduckdb.dylib present, bun/bun present and executable) so a misbuild fails at the runner
instead of on a user's machine.
The port work this workflow existed to de-risk has landed: dialogs are relayed to the shell
(/api/shell/dialogs), the macOS menu/lifecycle/file-open conventions are implemented, and
shortcuts are platform-correct. What remains is verification on real hardware — Phase 7 of
specs/based/plans/macos-port.md.
.\scripts\release.ps1 patch # or minor / major / -Version 1.0.0
.\scripts\release.ps1 patch -DryRun # bump + changelog draft only, publish nothingThe local half: preflight (clean tree, on main) → bump → draft a CHANGELOG.md section from the
commit log and stop for you to rewrite it → commit, tag, push. The tag push triggers
release.yml, which typechecks, tests, builds the Windows
installer (Inno Setup via choco) and the macOS .dmg on pinned runners, publishes one GitHub
release with both artifacts + SHA-256s + per-platform install notes, and bumps the
Cyronius/homebrew-based cask (needs the TAP_PUSH_TOKEN secret; skips with a warning if absent).
Version bumping alone:
.\scripts\bump-version.ps1 patch -WhatIf # print the transition, write nothing
.\scripts\bump-version.ps1 minorIt rewrites the version in shell-tauri/tauri.conf.json and shell-tauri/Cargo.toml (kept in
step — Cargo.toml feeds the exe's file-version metadata) and regenerates
core/src/version.ts, which is committed so a fresh clone typechecks
without running the script first. The version reaches the status bar via /api/health.
Builds happen in CI, not locally. release.ps1 used to build the installer on this machine
(Inno Setup + Rust toolchain required locally); Phase 6 of the
macOS port plan split it at the build boundary, so releases
are reproducible from the repository alone and both platforms build from the same tag.
Keep scripts/*.ps1 ASCII-only. Windows PowerShell 5.1 decodes .ps1 files as ANSI, so a
UTF-8 em-dash or curly quote — even inside a comment — corrupts the parse of the entire script.