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.tsas HTTP val (serves web interface and API) - Set
cronjob.tsas Cron val with schedule*/15 * * * *(every 15 minutes)
- 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:
PostTransformerconverts 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.socialmentions tohttps://bsky.app/profile/handle.bsky.sociallinks - 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