Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.rocksky.app/llms.txt

Use this file to discover all available pages before exploring further.

rocksky is a typed, pipe-friendly Gleam client.
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 as endpoint(...) |> rocksky.<param>(...) |> ... |> rocksky.send(client).
  • No Option(_) at the call site. Required params land in the endpoint constructor; optional params chain as functions on the Request(a) value.
  • Builders for body-heavy procedures. scrobble.create, song.create, shout.create, etc. accept a typed builder.
  • Request(a) is just data. It carries the method, params, headers, body and decoder. You can decorate it (rocksky.header for per-request headers) and only at send does the network happen.
  • Errors are explicit. RocksyError distinguishes transport errors, XRPC errors, raw HTTP failures, and decode failures.
  • Swappable transport. rocksky.with_send lets 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")
  |> rocksky.with_bearer_token("xxx")
  |> rocksky.with_user_agent("my-app/1.0")
  |> rocksky.with_header("x-trace-id", "abc123")

Builder vocabulary

These helpers live in the rocksky module and work on any Request(a):
FunctionXRPC 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

ModuleLexicon namespace
rocksky/actorapp.rocksky.actor.*
rocksky/albumapp.rocksky.album.*
rocksky/apikeyapp.rocksky.apikey.*
rocksky/artistapp.rocksky.artist.*
rocksky/chartsapp.rocksky.charts.*
rocksky/dropboxapp.rocksky.dropbox.*
rocksky/feedapp.rocksky.feed.*
rocksky/googledriveapp.rocksky.googledrive.*
rocksky/graphapp.rocksky.graph.*
rocksky/likeapp.rocksky.like.*
rocksky/mirrorapp.rocksky.mirror.*
rocksky/playerapp.rocksky.player.*
rocksky/playlistapp.rocksky.playlist.*
rocksky/scrobbleapp.rocksky.scrobble.*
rocksky/shoutapp.rocksky.shout.*
rocksky/songapp.rocksky.song.*
rocksky/spotifyapp.rocksky.spotify.*
rocksky/statsapp.rocksky.stats.*

Decoding Dynamic responses

Common views (Profile, Artist, Album, Song, Scrobble, Stats, Listener, Shout, ApiKey) are typed in rocksky/types. Inline anonymous shapes (e.g. feed search) are returned as Dynamic so you can decode 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
}

Types

Most public model types alias the lexicon-derived shapes in rocksky/generated/types, mirroring every lex *View* / *Record / *Input / *Output / *Params shape from the Rocksky lexicons. ApiKey and Shout are hand-written because they carry fields the lexicon does not yet declare. Regenerate with bun run lexgen:types at the repo root.

License

MIT © Tsiry Sandratraina. Source: sdk/gleam.