Elixir ATProtocol ingestion and sync library.

Agent Guidelines for Drinkup#

Commands#

  • Test: mix test (all), mix test test/path/to/file_test.exs (single file), mix test test/path/to/file_test.exs:42 (single test at line)
  • Format: mix format (auto-formats all code)
  • Lint: mix credo (static analysis), mix credo --strict (strict mode)
  • Compile: mix compile
  • Docs: mix docs
  • Type Check: mix dialyzer (if configured)

Code Style#

  • Imports: Use alias for modules (e.g., alias Drinkup.Firehose.{Event, Options}), require for macros (e.g., require Logger)
  • Formatting: Elixir 1.18+, auto-formatted via .formatter.exs with import_deps: [:typedstruct]
  • Naming: snake_case for functions/variables, PascalCase for modules, :lowercase_atoms for atoms, @behaviour (not @behavior)
  • Types: Use @type and @spec for all functions; use TypedStruct for structs with enforce: true for required fields
  • Moduledocs: Public modules need @moduledoc, public functions need @doc with examples
  • Error Handling: Return {:ok, result} or {:error, reason} tuples; use with for chaining operations; log errors with Logger.error("#{Exception.format(:error, e, __STACKTRACE__)}")
  • Pattern Matching: Prefer pattern matching in function heads over conditionals; use guard clauses when appropriate
  • OTP: Use child_spec/1 for custom supervisor specs; :gen_statem for state machines; Task.Supervisor for concurrent tasks; Registry for named lookups
  • Tests: Use ExUnit with use ExUnit.Case; use doctest Module for documentation examples
  • Dependencies: Core deps include gun (WebSocket), car (CAR format), cbor (encoding), TypedStruct (typed structs), Credo (linting)

Project Structure#

Each namespace (Drinkup.Firehose, Drinkup.Jetstream, Drinkup.Tap) follows a common architecture:

  • Core Modules:

    • Consumer - Behaviour/macro for handling events; use Namespace with handle_event/1 implementation
    • Event - Typed event structs specific to the protocol
    • Socket - :gen_statem WebSocket connection manager
    • Options (or top-level utility module) - Configuration and runtime utilities
  • Consumer Pattern: use Namespace, opts... with handle_event/1 callback; consumer module becomes a supervisor

Namespace-Specific Details#

  • Firehose (Drinkup.Firehose.*): Full AT Protocol firehose

    • Events: Commit, Sync, Identity, Account, Info
    • Additional: RecordConsumer macro for filtered commit records with handle_create/1, handle_update/1, handle_delete/1 callbacks
    • Pattern: use Drinkup.Firehose.RecordConsumer, collections: [~r/app\.bsky\.graph\..+/, "app.bsky.feed.post"]
  • Jetstream (Drinkup.Jetstream.*): Simplified JSON event stream

    • Events: Commit, Identity, Account
    • Config: :wanted_collections, :wanted_dids, :compress (zstd)
    • Utility: Drinkup.Jetstream.update_options/2 for dynamic filtering
    • Semantics: Fire-and-forget (no acks)
  • Tap (Drinkup.Tap.*): HTTP API + WebSocket indexer/backfill service

    • Events: Record, Identity
    • Config: :host, :admin_password, :disable_acks
    • Utility: Drinkup.Tap HTTP API functions (add_repos/2, remove_repos/2, get_repo_info/2)
    • Semantics: Ack/nack - return :ok/{:ok, _}/nil to ack, {:error, _} to nack (Tap retries)

Important Notes#

  • Update CHANGELOG.md when adding features, changes, or fixes under ## [Unreleased] with appropriate sections (Added, Changed, Fixed, Deprecated, Removed, Security)
  • WebSocket States: Socket uses :disconnected:connecting_http:connecting_ws:connected flow
  • Sequence Tracking: Use Event.valid_seq?/2 to validate sequence numbers from firehose