- TypeScript 97%
- JavaScript 2%
- CSS <1%
- Shell <1%
- Docker <1%
❤️ favs.blue #
A favstar.fm-style web app for Bluesky: type any handle and see that user's most-liked or most-reposted posts, filterable by "all time" or "last month."
Live engagement comes from the Bluesky Jetstream firehose. The first time a handle is looked up, a backfill job pulls historical posts from the Bluesky AppView API.
Stack: TypeScript / AdonisJS, Postgres for engagement counters and metadata, Edge.js templates with Alpine.js (CSP build) on the frontend. Ships as one Docker image with three process entrypoints (web, jetstream consumer, worker).
Local development #
Prerequisites: Mise, Docker.
# 1. Install Node/pnpm
mise trust && mise install
# 1. Copy the env template and generate an APP_KEY
cp .env.example .env
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
# paste the output into APP_KEY in .env
# 2. Start Postgres
docker compose up postgres -d
# 3. Install dependencies
pnpm install
# 4. Run migrations and start all three processes
pnpm dev
pnpm dev runs the web server, jetstream consumer, and worker concurrently with hot-reload. The app is at http://localhost:3333.
The first profile lookup for a new handle shows a progress bar while backfill runs. Live engagement updates flow in once the jetstream consumer has been running.
Useful commands #
pnpm test # Japa test runner
pnpm lint # eslint
pnpm typecheck # tsc --noEmit
pnpm format # prettier --write
pnpm build # production build
# Run a single test file
node ace test --files "tests/unit/services/jetstream_consumer.spec.ts"
# Apply Postgres migrations
node ace migration:run --force
Production (Docker Swarm) #
The repository ships a docker-compose.yml configured for docker stack deploy.
Architecture #
Three process entrypoints:
- HTTP web server (
bin/server.ts) — controllers inapp/controllers/, routes instart/routes.ts. Profile pages at/profile/:handle/likes|repostsare the main product surface. - Jetstream consumer (
node ace jetstream:consume) — connects to the Bluesky Jetstream WebSocket, filters for tracked DIDs, writes per-post engagement counter deltas to Postgres atomically with the cursor, and persists cursor position so it can resume after restart. - Worker (
node ace worker:run) — pg-boss worker that runs backfill jobs (fetch historical posts from AppView on first lookup) and scheduled cron tasks (engagement lookup pruning).
Configuration #
See .env.example for the full list of environment variables. start/env.ts is the source of truth — variables are validated there at boot.
License #
See LICENSE.