Elixir ATProtocol firehose & subscription listener
1# Drinkup 2 3An Elixir library for listening to events from an ATProtocol relay 4(firehose/`com.atproto.sync.subscribeRepos`). Eventually aiming to support any 5ATProtocol subscription. 6 7## TODO 8 9- Support for different subscriptions other than 10 `com.atproto.sync.subscribeRepo'. 11- Validation (signatures, making sure to only track handle active accounts, 12 etc.) (see 13 [Firehose Validation Best Practices](https://atproto.com/specs/sync#firehose-validation-best-practices)) 14- Look into backfilling? See if there's better ways to do it. 15- Built-in solutions for tracking resumption? (probably a pluggable solution to 16 allow for different things like Mnesia, Postgres, etc.) 17- Testing of multi-node/distribution. 18- Tests 19- Documentation 20 21## Installation 22 23Add `drinkup` to your `mix.exs`. 24 25```elixir 26def deps do 27 [ 28 {:drinkup, "~> 0.1"} 29 ] 30end 31``` 32 33Documentation can be found on HexDocs at https://hexdocs.pm/drinkup. 34 35## Example Usage 36 37First, create a module implementing the `Drinkup.Consumer` behaviour (only 38requires a `handle_event/1` function): 39 40```elixir 41defmodule ExampleConsumer do 42 @behaviour Drinkup.Consumer 43 44 def handle_event(%Drinkup.Event.Commit{} = event) do 45 IO.inspect(event, label: "Got commit event") 46 end 47 48 def handle_event(_), do: :noop 49end 50``` 51 52Then add Drinkup and your consumer to your application's supervision tree: 53 54```elixir 55defmodule MyApp.Application do 56 use Application 57 58 def start(_type, _args) do 59 children = [{Drinkup, %{consumer: ExampleConsumer}}] 60 Supervisor.start_link(children, strategy: :one_for_one) 61 end 62end 63``` 64 65You should then be able to start your application and start seeing 66`Got commit event: ...` in the terminal. 67 68### Record Consumer 69 70One of the main reasons for listening to an ATProto relay is to synchronise a 71database with records. As a result, Drinkup provides a light extension around a 72basic consumer, the `RecordConsumer`, which only listens to commit events, and 73transforms them into a slightly nicer structure to work around, calling your 74`handle_create/1`, `handle_update/1`, and `handle_delete/1` functions for each 75record it comes across. It also allows for filtering of specific types of 76records either by full name or with a 77[Regex](https://hexdocs.pm/elixir/1.18.4/Regex.html) match. 78 79```elixir 80defmodule ExampleRecordConsumer do 81 # Will respond to any events either `app.bsky.feed.post` records, or anything under `app.bsky.graph`. 82 use Drinkup.RecordConsumer, collections: [~r/app\.bsky\.graph\..+/, "app.bsky.feed.post"] 83 alias Drinkup.RecordConsumer.Record 84 85 def handle_create(%Record{type: "app.bsky.feed.post"} = record) do 86 IO.inspect(record, label: "Bluesky post created") 87 end 88 89 def handle_create(%Record{type: "app.bsky.graph" <> _} = record) do 90 IO.inspect(record, label: "Bluesky graph updated") 91 end 92 93 def handle_update(record) do 94 # ... 95 end 96 97 def handle_delete(record) do 98 # ... 99 end 100end 101``` 102 103## Special thanks 104 105The process structure used in Drinkup is heavily inspired by the work done on 106[Nostrum](https://github.com/Kraigie/nostrum), an incredible Elixir library for 107Discord. 108 109## License 110 111This project is licensed under the [MIT License](./LICENSE)