rocksky — Gleam SDK for the Rocksky XRPC API
A pipe-friendly, batteries-included Gleam client for the
Rocksky XRPC API, generated from the lexicons in
apps/api/lexicons.
gleam add rocksky
At a glance
Every endpoint returns a rocksky.Request(a). Refine it with chainable
helpers, then send it through a Client:
import gleam/io
import gleam/option
import rocksky
import rocksky/actor
import rocksky/scrobble
pub fn main() {
let client =
rocksky.new()
|> rocksky.with_bearer_token("xxx")
// GET app.rocksky.actor.getProfile
let assert Ok(profile) =
actor.get_profile(did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr")
|> rocksky.send(client)
io.println("Hello, " <> option.unwrap(profile.handle, "unknown") <> "!")
// Optional params chain naturally — no Some/None at the call site.
let assert Ok(scrobbles) =
actor.get_actor_scrobbles(did: "did:plc:7vdlgi2bflelz7mmuxoqjfcr")
|> rocksky.limit(50)
|> rocksky.offset(0)
|> rocksky.send(client)
// Procedures with rich bodies use a typed builder, ending in `create`:
let _ =
scrobble.new_scrobble(title: "Karma Police", artist: "Radiohead")
|> scrobble.with_album("OK Computer")
|> scrobble.with_duration_ms(263_000)
|> scrobble.create
|> rocksky.send(client)
}
Design
- One
Client, one pipe. Build the client once, then every call reads asendpoint(...) |> rocksky.<param>(...) |> ... |> rocksky.send(client). - No
Option(_)at the call site. Required params land in the endpoint constructor; optional params are added by chaining functions on theRequest(a)value. Skip them by simply not piping them. - Builders for body-heavy procedures.
scrobble.create,song.create,shout.create, etc. accept a typed builder (NewScrobble,NewSong) that flows throughwith_*setters before becoming aRequest. Request(a)is just data. It carries the method, params, headers, body and decoder. You can pass it around, decorate it (rocksky.headerfor per-request headers), and only atsenddoes the network happen.- Errors are explicit.
RocksyErrordistinguishes transport errors, XRPC errors, raw HTTP failures, and decode failures. - Swappable transport.
rocksky.with_sendlets you plug in your own HTTP function (great for tests; required if you target JavaScript).
Configuration
let client =
rocksky.new()
|> rocksky.with_base_url("https://api.rocksky.app") // default
|> rocksky.with_bearer_token("xxx")
|> rocksky.with_user_agent("my-app/1.0")
|> rocksky.with_header("x-trace-id", "abc123")
Builder vocabulary
These chainable helpers live in the rocksky module and work on any
Request(a):
| Function | XRPC parameter |
|---|---|
rocksky.limit(n) | limit |
rocksky.offset(n) | offset |
rocksky.cursor(c) | cursor |
rocksky.start_date(d) | startDate |
rocksky.end_date(d) | endDate |
rocksky.genre(g) | genre |
rocksky.year(y) | year |
rocksky.size(n) | size |
rocksky.param(name, value) | arbitrary string |
rocksky.int_param(name, value) | arbitrary int |
rocksky.bool_param(name, value) | arbitrary bool |
rocksky.repeated_param(name, vs) | array (repeats key) |
rocksky.header(name, value) | per-request header |
Namespace-specific params (e.g. charts.with_artist_uri, graph.with_dids,
player.with_player_id) live in their own module so the global vocabulary
stays small.
Endpoint modules
The SDK mirrors the lexicon namespaces. Each module hosts the queries and
procedures under app.rocksky.<namespace>:
| Module | Lexicon namespace |
|---|---|
rocksky/actor | app.rocksky.actor.* |
rocksky/album | app.rocksky.album.* |
rocksky/apikey | app.rocksky.apikey.* |
rocksky/artist | app.rocksky.artist.* |
rocksky/charts | app.rocksky.charts.* |
rocksky/dropbox | app.rocksky.dropbox.* |
rocksky/feed | app.rocksky.feed.* |
rocksky/googledrive | app.rocksky.googledrive.* |
rocksky/graph | app.rocksky.graph.* |
rocksky/like | app.rocksky.like.* |
rocksky/mirror | app.rocksky.mirror.* |
rocksky/player | app.rocksky.player.* |
rocksky/playlist | app.rocksky.playlist.* |
rocksky/scrobble | app.rocksky.scrobble.* |
rocksky/shout | app.rocksky.shout.* |
rocksky/song | app.rocksky.song.* |
rocksky/spotify | app.rocksky.spotify.* |
rocksky/stats | app.rocksky.stats.* |
Decoding Dynamic responses
Common views (Profile, Artist, Album, Song, Scrobble, Stats,
Listener, Shout, ApiKey) are typed in rocksky/types. For inline,
anonymous JSON objects (e.g. feed search, chart shapes) the SDK types the
response as Dynamic so you can decode it on your terms:
import gleam/dynamic/decode
import rocksky
import rocksky/decoders
import rocksky/feed
let assert Ok(payload) =
feed.search(q: "radiohead") |> rocksky.send(client)
let result_decoder = {
use artists <- decode.optional_field("artists", [], decode.list(decoders.artist()))
use songs <- decode.optional_field("songs", [], decode.list(decoders.song()))
decode.success(#(artists, songs))
}
let assert Ok(#(artists, songs)) = decode.run(payload, result_decoder)
Error handling
import rocksky/error
let result =
actor.get_profile(did: "garbage") |> rocksky.send(client)
case result {
Ok(p) -> // ...
Error(error.XrpcError(status: _, name: "InvalidRequest", message: m)) ->
// Server told us why
Error(error.TransportError(_)) ->
// DNS, TLS, etc.
Error(error.HttpStatusError(status: _, body: _)) ->
// Non-XRPC 4xx/5xx
Error(error.DecodeError(_)) ->
// Server returned JSON we didn't expect
Error(error.InvalidInput(_)) ->
// Caught client-side before sending
}
Reaching un-surfaced XRPC methods
If the SDK is missing an endpoint, drop down to the underlying constructors:
import gleam/dynamic/decode
let _ =
rocksky.query("app.rocksky.some.newEndpoint", decode.dynamic)
|> rocksky.param("foo", "bar")
|> rocksky.send(client)
Examples
Runnable examples live under src/examples/ so they are
compile-checked against the SDK on every gleam build. Run any of them with
gleam run -m examples/<name>:
profile.gleam— fetch a profile and print itscrobble.gleam— record a play (builder)paginate_scrobbles.gleam— walk a user's scrobble history in pagessearch.gleam— search and decode resultswrapped.gleam— fetch year-in-review statscustom_transport.gleam— swap the HTTP backend (for tests / JS targets)
Testing
gleam test
The SDK is built around an injectable transport, so unit tests don't need a
network. See test/rocksky/client_test.gleam for the mock-send pattern.
License
MIT © Tsiry Sandratraina.