馃尶 Collaborative wiki on ATProto
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Getting started#

Prerequisites: Bun and Node.js 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 to pin Node 22: mise use node@22. Or nvm: nvm use 22.

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#

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.