This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Run all tests deno test --allow-import # Run specific test file deno test --allow-import tests/sync-service.test.ts deno test --allow-import tests/post-transformer.test.ts deno test --allow-import tests/storage.test.ts # Run single test case deno test --allow-import tests/sync-service.test.ts --filter "should sync posts successfully"
# Lint all files deno lint # Format code deno fmt # Type check deno check backend/index.ts deno check cronjob.ts
- Set
backend/index.ts
as HTTP val (serves web interface and API) - Set
cronjob.ts
as Cron val with schedule*/15 * * * *
(every 15 minutes) - You can
vt push
to push changes to valtown so you can test the updated online endpoints
- Make it a point to run deno lint, test and fmt after any big change
This is a single-user bridge service that cross-posts from Bluesky to Mastodon, built specifically for Val.town with dependency injection for testability.
The codebase follows a dependency injection pattern with clear separation between:
- Interfaces (
backend/interfaces/
) - Abstract contracts for storage and HTTP clients - Implementations (
backend/storage/
,backend/services/
) - Concrete implementations - Tests (
tests/
) - Use in-memory mocks for fast, isolated testing
Storage Layer: Uses abstract StorageProvider
interface with SQLite
production implementation and in-memory test implementation. All database
operations go through this abstraction.
HTTP Client Layer: ATProto and Mastodon interactions are abstracted through interfaces, allowing mock implementations for testing.
Service Layer: SyncService
accepts dependencies via constructor injection,
making it fully testable without external dependencies.
OAuth Flow: Two separate OAuth implementations (ATProto uses PKCE, Mastodon
uses traditional OAuth2) handled in backend/routes/oauth.ts
.
- Setup: User runs setup wizard → OAuth tokens stored in SQLite
- Sync: Cron job (
cronjob.ts
) →SyncService.syncAllUsers()
→ fetches posts from ATProto → transforms content → posts to Mastodon - Transformation:
PostTransformer
converts Bluesky mentions to profile links since Mastodon handles don't exist cross-platform - Tracking: Every post sync is tracked in database with status (pending/success/failed) and retry logic
backend/index.ts
- Main HTTP server (Hono app) serving setup wizard and OAuth callbackscronjob.ts
- Scheduled sync job (runs every 15 minutes)backend/services/sync-service-di.ts
- Main sync logic with dependency injectionbackend/interfaces/
- Abstract contracts for testabilitybackend/storage/
- SQLite (production) and in-memory (testing) implementationsshared/types.ts
- TypeScript interfaces shared between frontend and backend
Tests use in-memory storage and mock HTTP clients for fast, isolated testing:
InMemoryStorageProvider
- Full storage implementation in memoryMockATProtoClient
/MockMastodonClient
- Controllable mock API clients- Tests verify business logic without external dependencies
Built for Val.town's serverless environment:
- Uses Val.town's SQLite hosting (
https://esm.town/v/stevekrouse/sqlite
) - Uses Val.town utility functions (
https://esm.town/v/std/utils
) - Follows Val.town's file serving patterns for static assets
- Environment variables for OAuth configuration
ATProto OAuth: Uses PKCE flow with client metadata discovery. Client
metadata must be publicly accessible at ATPROTO_CLIENT_ID
URL.
Mastodon OAuth: Automatically registers app with user's Mastodon instance during setup flow.
The PostTransformer
handles the key business logic:
- Converts
@handle.bsky.social
mentions tohttps://bsky.app/profile/handle.bsky.social
links - Extracts and processes media (images/videos)
- Generates content hashes for duplicate prevention
- Handles ATProto facets (mentions, hashtags, links)
Implements exponential backoff retry mechanism:
- 3 retry attempts by default
- Base delay: 1 second, max delay: 30 seconds
- Distinguishes between retryable (network, 5xx) and non-retryable errors
- All errors logged to database for debugging