Development

How to build, test, and run each part of the repository. For what each part is, see the Codebase tour; for how to propose a change, Contribute.

Tools to install

ToolNeeded forInstall
Rust (rustup)Everything in crates/curl https://sh.rustup.rs -sSf | sh. The version is pinned in rust-toolchain.toml; rustup installs it on first use.
uvPython package and benchmarkscurl -LsSf https://astral.sh/uv/install.sh | sh
BunWebsite and TypeScript clientcurl -fsSL https://bun.sh/install | bash
GoThe benchmark proxy onlybrew install go
LLVM, wasm-bindgenRebuilding the WASM reader onlybrew install llvm (Linux: clang and llvm packages), rustup target add wasm32-unknown-unknown, cargo install wasm-bindgen-cli --version 0.2.129 (must match crates/timelake-wasm/Cargo.lock)
prekGit hooksuv tool install prek

First setup

uv sync                          # Python environment in ./.venv (builds the Python package)
bun install                      # JavaScript dependencies
prek install                     # Git hooks: formatters and fast lints on commit, clippy and type checks on push

Check everything the way CI does:

prek run --all-files --hook-stage pre-push       # formatters, linters and type checkers for every language
cargo test --workspace                           # Rust tests
uv run pytest crates/timelake-py/tests           # Python package tests
uv run pytest bench/tests                        # benchmark helper tests
cargo build -p timelake-cli && (cd packages/timelake-client && bun test)   # TypeScript client vs the engine

Rust: engine and CLI

cargo build --release -p timelake-cli        # → target/release/timelake
cargo test --workspace                          # tests
cargo clippy --workspace --all-targets          # lints (pedantic set, see [workspace.lints] in Cargo.toml)
cargo fmt --all                                 # formatting (rustfmt.toml: width 120)
cargo doc --no-deps --open                      # API docs

The Cargo workspace (root Cargo.toml) holds timelake, timelake-format, timelake-cli, and timelake-py, with one Cargo.lock and one target/. timelake-wasm is built separately for WebAssembly (see below).

Try the CLI on a throwaway warehouse:

export TIMELAKE_WAREHOUSE=/tmp/tl-dev
target/release/timelake import iot.readings readings.csv --time-column time --series-key device_id
target/release/timelake sql "SELECT count(*) FROM iot.readings"

Python package

uv sync                                    # at the repo root: builds the package into ./.venv
uv run pytest crates/timelake-py/tests
uv run ruff format . && uv run ruff check .  # formatting and lints (config in the root pyproject.toml)
uv run ty check                            # type check

The root pyproject.toml is a uv workspace with two members: the package (crates/timelake-py) and the benchmarks (bench). uv sync and uv run rebuild the native module with maturin whenever Rust sources change. The Python files live in python/timelake; the native part is imported as timelake._timelake.

WASM reader and TypeScript client

bun install                                         # at the repo root (bun workspace)
cd packages/timelake-client
bun run build:wasm                                  # after changing crates/timelake-wasm or timelake-format (about 4 minutes)
bun run check:wasm                                  # is the embedded WASM current?
bun run typecheck

The client tests build a small warehouse with the CLI in a temporary folder, serve it locally, and compare the client's answers with the CLI's. They never touch the network:

cargo build -p timelake-cli                         # the tests use target/debug/timelake (or $TIMELAKE_CLI)
(cd packages/timelake-client && bun test)

Website

bun install                       # at the repo root
cd dashboard
bun run dev                       # http://127.0.0.1:3000
bun run typecheck                 # Next.js route types, then tsc
bun run build                     # production build, as Vercel runs it

Linting and formatting run from the repo root for all JavaScript and TypeScript: bun run lint (oxlint) and bun run format (oxfmt).

Docs pages are Markdown (MDX) files in dashboard/app/(site)/docs/<page>/page.mdx; the sidebar order is in dashboard/lib/docs-nav.ts. After re-running benchmarks, copy the results the site uses with bun run sync-results.

Benchmarks

uv sync                                       # at the repo root; the same environment as above
cargo build --release -p timelake-cli         # the Timelake reader
GOBIN=$PWD/bench/.bin go install github.com/timescale/tsbs/cmd/tsbs_generate_data@v0.0.0-20260527045238-8323e59c7402
uv run timebench run tsbs-iot --scale small
uv run timebench report latest

Then follow bench/README.md (workloads, systems, storage targets, adding your own) or bench/infra/README.md (AWS and the weekly run). Benchmark data lives in bench/data (git-ignored, several GB). Commit a run file from bench/results/runs only when it should join the history on the website.

Code style

The code guide for people and AI agents is CONTRIBUTING.md; agents also read AGENTS.md (and CLAUDE.md, which imports it).

LanguageFormatLintTypes
Rustrustfmt (width 120)clippy, pedantic setthe compiler
Pythonruff format (width 120)ruff (broad rule set, Google-style docstrings)ty; full annotations
TypeScriptoxfmt (width 120, no semicolons)oxlinttsc, strict with noUncheckedIndexedAccess
Gogofmtgo vetthe compiler
  • Errors: thiserror in Rust libraries, anyhow in the CLI; specific exception types in Python.
  • Comments: explain why, not what. Public items get a short doc comment.
  • Commits: short imperative subject with the area first, such as bench: …, Site: …, or Spec: ….

Continuous integration

GitHub Actions (.github/workflows/ci.yml) runs on every push to main and every pull request, with no secrets:

JobChecks
Rustcargo fmt --check, clippy with warnings as errors, cargo test --workspace
WASM readerfmt, clippy for wasm32 and the host, unit and golden tests
Pythonuv sync --locked, ruff format and lint, ty, the package and benchmark tests
TypeScriptoxlint, oxfmt, tsc, the embedded WASM matches its sources, website results match bench/results
TypeScript clientclient tests against a debug build of the CLI
Gogofmt, go vet

Vercel builds and deploys the website separately, on every push to main. Other branches and pull requests get no preview deployment (git.deploymentEnabled in dashboard/vercel.json); run bun run dev to see a change.

Formatting-only commits are listed in .git-blame-ignore-revs; run git config blame.ignoreRevsFile .git-blame-ignore-revs once so git blame skips them.