## Getting started Prerequisites: [Bun](https://bun.sh) and [Node.js](https://nodejs.org) v22. Bun runs the appview and tests. Node is needed for the local ATProto PDS (`@atproto/pds` requires `better-sqlite3` which only builds against Node 22). Use [mise](https://mise.jdx.dev) to pin Node 22: `mise use node@22`. Or nvm: `nvm use 22`. ```bash bun install bun run dev # appview on :3000 (watch mode) bun run test:unit # unit tests (no Node needed) mise run test # full suite (unit + integration, needs Node 22) bun run lint # check with Biome bun run typecheck # tsgo --noEmit ``` The database auto-seeds with sample data on first run. To reset, delete `lichen.db` and restart. ## Full local stack `bun run dev:full` starts a test PDS (Node), the appview, and the firehose subscriber. Two test accounts are created automatically. Log in via: - `http://localhost:3000/dev/login/alice.test` - `http://localhost:3000/dev/login/bob.test` ## All commands ```bash bun run dev # appview server (watch mode) bun run dev:full # full local stack: PDS + appview + firehose bun run dev:firehose # firehose subscriber only bun run dev:viz # watch-mode viz bundle bun run dev:editor # watch-mode editor bundle bun run test:unit # unit tests only (no Node needed) mise run test:integration # integration tests (Node 22 for PDS subprocess) mise run test # full suite (unit + integration) bun run lint:fix # auto-fix lint/format bun run typecheck # type check bun run build:css # build Tailwind CSS bun run build:viz # bundle viz renderers bun run build:editor # bundle editor ``` ## Project layout ``` src/ server/ # Elysia routes, database (schema, queries/, seed) atproto/ # OAuth, PDS write functions firehose/ # standalone firehose subscriber lib/ # shared logic (diff, markdown, slugs, viz, image, blob, i18n, urls...) shared/ # shared type definitions views/ # HTML templates lexicons/ # ATProto lexicon schemas public/ # static assets (editor, viz, CSS) scripts/ # dev tooling tests/ # mirrors src/ structure ``` ## Code style - TypeScript, strict mode - Biome for formatting (tabs, double quotes), run `bun run lint:fix` before committing - Named exports, no default exports - Functions over classes unless state is needed - Throw typed error classes (`WikiNotFoundError`, `AccessDeniedError`, `PdsWriteError`), caught by a single `onError` hook. Route handlers never catch errors themselves. - Route handlers stay thin: orchestrators in `src/lib/orchestrators/` own the PDS-then-DB lifecycle. Routes parse input, call the orchestrator, redirect. - PDS write must succeed before DB write. If PDS fails, the request fails. - No ORMs, raw SQL with prepared statements - UI strings go through `src/lib/i18n/`, no hardcoded English in views - Tailwind classes come from `src/views/theme.ts` (`THEME.*`), no hardcoded class strings in views ## Database SQLite via `bun:sqlite`. Schema in `src/server/db/schema.ts`, queries in `src/server/db/queries/`. All queries use prepared statements, writes are wrapped in transactions. ## ATProto Lexicon namespace: `wiki.lichen` (domain: `lichen.wiki`). Schemas in `lexicons/`. Record types: `wiki.lichen.wiki`, `wiki.lichen.note`, `wiki.lichen.noteRevision`, `wiki.lichen.memberRequest`, `wiki.lichen.membership`, `wiki.lichen.bookmark` In dev mode (no OAuth configured), PDS writes are skipped and a mock DID is used. ## Tests Tests mirror `src/` under `tests/`. All test files share one in-memory DB singleton (`tests/preload.ts` sets `DB_PATH=:memory:`), so tests that create data must clean up in `afterAll`. Integration tests in `tests/integration/` write real ATProto records to a PDS subprocess and verify they flow through the firehose into the appview DB. The PDS uses `better-sqlite3` (native addon), which requires Node 22. Run them via `mise run test:integration` to ensure Node 22 is on PATH.