Skip to content

Contributing

This page is for people who want to change the code. It covers the day-to-day workflow, the test suites, and the conventions the codebase follows.

Prerequisites

  • Bun 1.3 or newer — the repo pins a specific version in .bun-version. CI uses oven-sh/setup-bun@v2 with bun-version-file: .bun-version.
  • A working internet connection, the first time you run bun install (to download the workspace catalog dependencies).
  • A POSIX-ish terminal. Windows works through WSL or PowerShell with the same commands.

First-time setup

bash
git clone https://github.com/sreekarnv/rewind
cd rewind
bun install

bun install at the root reads the workspace catalog and installs every package's dependencies. The single bun.lock at the root is the source of truth; do not commit a nested lockfile.

Running

From the repo root:

bash
bun run dev              # both API and web in parallel
bun run dev:api          # API only
bun run dev:web          # web only
bun run dev:docs         # documentation only
bun run dev:clean        # kill anything on 5173/8000, then start dev

The dev script uses bun run --parallel to run the API and the web app side by side. If you want logs split into separate terminals, use the targeted scripts.

The API defaults to 127.0.0.1:8000, the web app to localhost:5173, and the docs to localhost:3000. To override the API port, set PORT. To override the data directory, set REWIND_DATA_DIR — see Environment variables for the full list.

Verifying

bash
bun run check            # typecheck both packages
bun run test             # unit tests for both packages
bun run build            # production builds (web, api, docs)
bun run verify           # check + test + build

bun run verify is the full gate. CI runs the same sequence on every push and pull request.

Per-package commands

bash
bun run --filter '@rewind/api' check
bun run --filter '@rewind/web' test
bun run --filter '@rewind/docs' build

--filter '@rewind/*' --if-present runs the named script in every workspace that defines it. That's how the root verify script composes checks, tests, and builds.

End-to-end tests

The web app has a small Playwright suite under apps/web/tests/e2e/. To run it:

bash
bun run test:e2e

Playwright needs the web app running. The script handles starting the dev server, but if you're running the suite outside of CI, make sure the API is also up (bun run dev:api in another terminal). The first time you run the suite, Playwright will ask to download Chromium; pass --with-deps on Linux:

bash
bunx playwright install --with-deps chromium

Database commands

Drizzle commands are forwarded to the API workspace from the root:

bash
bun run db:generate      # regenerate SQL migrations from src/db/schema.ts
bun run db:migrate       # apply pending migrations
bun run db:studio        # open Drizzle Studio in the browser

Drizzle Studio is a small web UI for the database. It runs on its own port and connects to the same SQLite file as the API.

If you change the schema, edit apps/api/src/db/schema.ts, then run bun run db:generate and commit both the schema and the generated SQL. Don't hand-edit the generated SQL — Drizzle will overwrite it next time.

Tests in this repo

There are three test locations:

  • apps/api/test/ — Bun test files. Use bun:test. Network calls are exercised with the Elysia app in-process.
  • apps/web/tests/ — Bun test files for the web app's pure modules (mappers, formatters).
  • apps/web/tests/e2e/ — Playwright tests for the UI flow. The current suite is a small "stabilization" test that exercises the workspace page.

Code conventions

  • TypeScript everywhere. No JS in source files.
  • Svelte 5 runes in .svelte.ts state modules. requestState, collectionsState, etc. are $state(...) objects. Don't bring in stores or writable.
  • Eden Treaty for the web-to-API client. The export of createApp is the source of truth for the wire types; do not duplicate them in apps/web/src/lib/api/types.ts. Add derived aliases there if you need them.
  • Backend layout follows the Elysia best-practices split described in Architecture → Backend modules. In short: index.ts boots, app.ts is the composition root, core/<domain>/ holds the services, modules/<resource>/ holds the thin route handlers, and http/ holds the shared response envelope and middlewares.
  • Path alias — the API uses the @/ alias (configured in apps/api/tsconfig.json) for imports inside apps/api/src/. Inside a service, import another module with import { foo } from "@/core/...", not with a long relative chain.
  • Elysia models come in two flavours:
    • Shared models (used by more than one module, or the Api.Error / Api.IdParams shapes) live in apps/api/src/api/models.ts and apps/api/src/modules/shared.model.ts.
    • Module-local models live next to the route in apps/api/src/modules/<resource>/model.ts, exporting a …ModelRegistry. The module's index.ts does .use(<Registry>) and references the model by string (body: "Request.SendInput", response: { 200: "Request.SendResponseV2" }).
  • Domain services are plain factory functions in apps/api/src/core/<domain>/<domain>.service.ts of the form coreService(deps) → { …methods }. They are the only thing that talks to the database. New service methods go in the matching core/<domain>/ file — not in db/repository.ts, which now only holds the shared error classes.
  • Drizzle is used for the database. New tables go in apps/api/src/db/schema.ts; the generated SQL migrations are in apps/api/drizzle/. After editing the schema, run bun run db:generate and commit both files.
  • Error contract — every route returns errors with the centralized Api.Error shape from apps/api/src/http/response.ts. Use the notFound and errorEnvelope helpers instead of constructing the body inline. The error codes are the union in ApiErrorCode (validation_error, request_validation_error, not_found, forbidden, conflict, internal_error).
  • No comments unless asked. The codebase intentionally has very few comments. Add a comment only when the code is doing something non-obvious that a future reader cannot infer from the names.
  • No nested lockfiles. One bun.lock at the root.
  • One source of truth for shared versions — root package.json workspaces.catalog. Don't add a package.json field to a workspace for elysia, typescript, etc.
  • Kebab-case for user-facing files and URLs — including the VitePress site (this documentation). New docs go under docs/<section>/<topic>.md with a kebab-case filename.

Pull requests

  • Keep changes small and focused. Multiple unrelated changes should be separate PRs.
  • Make sure bun run verify passes locally before pushing.
  • Include a screenshot or a short screen recording for any UI change.
  • Update the docs if you change user-visible behavior. The docs site is in this repo, so PRs that need doc updates should include them.

CI

CI runs on every push and pull request. The job:

  1. Sets up Bun with the version pinned in .bun-version.
  2. Runs bun install --frozen-lockfile.
  3. Runs bun run check.
  4. Runs bun run test.
  5. Runs bun run build.
  6. Installs Playwright Chromium.
  7. Runs bun run test:e2e.

The workflow file is at .github/workflows/ci.yml. A separate .github/workflows/release.yml builds the release artifacts on tag pushes.

What's next?

Released under the MIT License.