FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
tijs

tijs

atproto-to-fediverse

Syncing Bluesky posts to Mastodon - Prod
Public
Like
1
atproto-to-fediverse
Home
Code
16
.claude
1
backend
7
frontend
8
shared
1
tests
5
.gitignore
.vtignore
CLAUDE.md
README.md
client-metadata-short.json
client-metadata.json
client-metadata.json.template
cookies.txt
C
cronjob.ts
deno.json
val-town.md
Branches
1
Pull requests
Remixes
2
History
Environment variables
3
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
CLAUDE.md
Code
/
CLAUDE.md
Search
7/18/2025
Viewing readonly version of main branch: v154
View latest version
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Development Commands

Testing

# 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"

Linting

# Lint all files deno lint # Format code deno fmt # Type check deno check backend/index.ts deno check cronjob.ts

Val.town Deployment

  • Set backend/index.ts as HTTP val (serves web interface and API)
  • Set cronjob.ts as Cron val with schedule */15 * * * * (every 15 minutes)

Workflow Recommendations

  • Make it a point to run deno lint, test and fmt after any big change

Architecture Overview

This is a single-user bridge service that cross-posts from Bluesky to Mastodon, built specifically for Val.town with dependency injection for testability.

Core Architecture Pattern

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

Key Architectural Components

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.

Data Flow

  1. Setup: User runs setup wizard → OAuth tokens stored in SQLite
  2. Sync: Cron job (cronjob.ts) → SyncService.syncAllUsers() → fetches posts from ATProto → transforms content → posts to Mastodon
  3. Transformation: PostTransformer converts Bluesky mentions to profile links since Mastodon handles don't exist cross-platform
  4. Tracking: Every post sync is tracked in database with status (pending/success/failed) and retry logic

File Structure Logic

  • backend/index.ts - Main HTTP server (Hono app) serving setup wizard and OAuth callbacks
  • cronjob.ts - Scheduled sync job (runs every 15 minutes)
  • backend/services/sync-service-di.ts - Main sync logic with dependency injection
  • backend/interfaces/ - Abstract contracts for testability
  • backend/storage/ - SQLite (production) and in-memory (testing) implementations
  • shared/types.ts - TypeScript interfaces shared between frontend and backend

Testing Strategy

Tests use in-memory storage and mock HTTP clients for fast, isolated testing:

  • InMemoryStorageProvider - Full storage implementation in memory
  • MockATProtoClient / MockMastodonClient - Controllable mock API clients
  • Tests verify business logic without external dependencies

Val.town Specifics

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

OAuth Implementation Details

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.

Content Transformation Logic

The PostTransformer handles the key business logic:

  • Converts @handle.bsky.social mentions to https://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)

Error Handling & Retry Logic

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
FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareersBrandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.