glyde/client
The driver: runs glyde/gateway’s outputs against transport functions you
hand it. Nothing here blocks, spawns, sleeps or returns a Result.
Three of the six adapter rules in glyde/gateway are kept here: outputs
run in order, the shard is committed before any of them runs, and a bot
arrives seeded. The other three are on the Transport you hand it.
Types
Opaque, unlike the shard inside it: the queue is what stops a handler’s
command re-entering the machine mid-batch. Read it with shard and state.
pub opaque type Bot(state)
The zlib context for gateway.ZlibStream. A native mutable resource, so it
lives out here rather than in the state machine.
This, not Config.compression, decides the mode: the bot writes the config
to match whichever of these it holds. Compression negotiated with nothing
to inflate with is a socket whose every frame is unreadable, so it is not
something a builder can reach.
pub type Inflater(state) {
Plaintext
Zlib(
inflate: fn(state, gateway.Conn, gateway.Stamp, BitArray) -> state,
reset: fn(state, gateway.Conn) -> state,
)
}
Constructors
-
PlaintextNo context, and
gateway.NoCompressionwith it. -
Zlib( inflate: fn(state, gateway.Conn, gateway.Stamp, BitArray) -> state, reset: fn(state, gateway.Conn) -> state, )One context per connection, and
gateway.ZlibStreamwith it.inflateanswers by callinginflated;resetstarts a fresh one, which every new connection needs.
What a handler gives back. Commands go through the machine rather than straight down the socket: they come out of the connection’s budget.
pub type Next(state) {
Next(state: state, commands: List(command.Command))
}
Constructors
-
Next(state: state, commands: List(command.Command))
How a connection earns the right to send IDENTIFY. Discord meters it per bot, not per connection, so a fleet takes turns.
pub type Slots(state) {
Solo
Fleet(
request: fn(state, gateway.Conn) -> state,
release: fn(state, gateway.Conn) -> state,
)
}
Constructors
-
SoloGrant every request immediately. Right for a single-shard bot.
-
Fleet( request: fn(state, gateway.Conn) -> state, release: fn(state, gateway.Conn) -> state, )Ask a coordinator,
glyde/identify_queuebeing one, and callslot_grantedwhen it answers.releasefrees the slot again.
The six things a shard asks of the outside world. Every function takes the bot’s state first and returns it, so a fake one can record every frame.
pub type Transport(state) {
Transport(
open: fn(state, gateway.Conn, String, String) -> state,
send: fn(state, frame.Outbound) -> state,
close: fn(state, Int) -> state,
drop: fn(state) -> state,
arm: fn(state, gateway.Timer, Int, gateway.Stamp) -> state,
cancel: fn(state, gateway.Timer) -> state,
)
}
Constructors
-
Transport( open: fn(state, gateway.Conn, String, String) -> state, send: fn(state, frame.Outbound) -> state, close: fn(state, Int) -> state, drop: fn(state) -> state, arm: fn(state, gateway.Timer, Int, gateway.Stamp) -> state, cancel: fn(state, gateway.Timer) -> state, )Arguments
- open
-
Open a WebSocket to
wss://<host><path>. Keep theConnand hand it back on every input from this socket, and do not touch the path. - send
-
Write
frame.textas one text frame. A failed write isclosed(bot, conn, None), never a raise: raising abandons the batch, heartbeat timer and all.frame.opsays what it was, for logging, so no adapter has to parse the payload back. - close
-
Write a close frame with this code, then tear the socket down. Only ever 1000, which ends the session, or 4000, which keeps it resumable.
- drop
-
Abandon the socket with no close frame. The peer has already gone, or there was never a socket.
- arm
-
Arm a one-shot timer, replacing whatever was armed under the same name. When it fires, call
timer_firedwith this exact stamp. - cancel
-
Cancel a timer. Cancelling one that is not armed is a no-op, and a fire that beats the cancel is recognised by its stamp and dropped.
Values
pub fn closed(
bot: Bot(state),
conn: gateway.Conn,
code: option.Option(Int),
) -> Bot(state)
The socket closed. None is a transport that died with no close frame;
1005 and 1006 mean the same thing and normalise to it.
pub fn collecting(
events: List(gateway.Event),
event: gateway.Event,
) -> Next(List(gateway.Event))
A handler that keeps every event, newest first, for the “hand the events back to me” shape.
pub fn command(
bot: Bot(state),
command: command.Command,
) -> Bot(state)
Send a gateway command. Goes out when the shard is live and has budget, and waits in the shard’s own queue otherwise, so it survives a reconnect.
pub fn conn(bot: Bot(state)) -> gateway.Conn
The connection the bot will hear from. An adapter uses the Conn from
Transport.open instead: synthesising one defeats the staleness check.
pub fn discarding() -> Transport(state)
Performs nothing. The explicit choice for a test that only wants to read the shard, and the only way to build a bot that reaches no socket.
pub fn feed(bot: Bot(state), input: gateway.Input) -> Bot(state)
Any input, for something this module has not given a name to.
pub fn from_shard(
shard shard: gateway.Shard,
state state: state,
transport transport: Transport(state),
) -> Bot(state)
A bot around a shard you built yourself, for a custom seed or for
gateway.resuming after a process restart.
The transport is taken here and not attached later, because a bot without
one advances its shard and writes to nothing. Pass discarding() when that
is what you want.
Compression starts off whatever the config asked for: with_inflater is
the switch, and it moves both halves together.
pub fn inflated(
bot: Bot(state),
conn: gateway.Conn,
stamp: gateway.Stamp,
result: Result(String, gateway.InflateFailure),
) -> Bot(state)
The answer to an Inflater.inflate. Error names which way it failed, and
no amount of retrying fixes either mid-connection.
pub fn is_terminal(bot: Bot(state)) -> Bool
True once the bot has stopped, or hit a close code no reconnect can fix. An adapter’s loop exits here.
pub fn new(
token token: String,
intents intents: intents.Intents,
transport transport: Transport(Nil),
) -> Bot(Nil)
Default gateway config, no state.
pub fn on_event(
bot: Bot(state),
handle: fn(state, gateway.Event) -> Next(state),
) -> Bot(state)
Fold events into the state. Replaces the previous handler.
pub fn on_notice(
bot: Bot(state),
notice: fn(state, gateway.Notice) -> state,
) -> Bot(state)
Watch the diagnostics: a zombie connection, a dropped command, a wait for an identify slot. Never protocol-significant, and where a log line belongs.
pub fn open_failed(
bot: Bot(state),
conn: gateway.Conn,
failure: gateway.DialFailure,
) -> Bot(state)
The WebSocket could not be established, or died before the upgrade finished. An adapter that saw a response status says so: 401 is a dead token, and the shard halts on it instead of redialling.
pub fn opened(bot: Bot(state), conn: gateway.Conn) -> Bot(state)
The WebSocket is up and the upgrade finished.
pub fn received(
bot: Bot(state),
conn: gateway.Conn,
text: String,
) -> Bot(state)
A text frame arrived, carrying exactly one gateway payload.
pub fn received_bytes(
bot: Bot(state),
conn: gateway.Conn,
data: BitArray,
) -> Bot(state)
A binary frame arrived under a compressed transport. A fragment, a payload or several; the core buffers.
pub fn sending(
next: Next(state),
command: command.Command,
) -> Next(state)
Send a gateway command once the current batch is done.
pub fn session(bot: Bot(state)) -> option.Option(gateway.Session)
The session, if there is one to resume with. Persist it across a process
restart and boot the next one with gateway.resuming.
pub fn shard(bot: Bot(state)) -> gateway.Shard
The pure core, for calling gateway.step directly or reading a phase.
pub fn slot_granted(
bot: Bot(state),
conn: gateway.Conn,
) -> Bot(state)
The fleet coordinator granted this connection its identify slot. Only
needed under Slots.Fleet.
pub fn start(bot: Bot(state)) -> Bot(state)
Begin. Arms the reconnect timer at zero, so the dial happens on the fire and there is exactly one code path that dials.
pub fn stateful(
config config: gateway.Config,
state state: state,
transport transport: Transport(state),
) -> Bot(state)
A bot carrying a state of your own, and the way to the rest of the config:
sharding, the tuning numbers. Nothing to remember is Nil.
pub fn stop(bot: Bot(state)) -> Bot(state)
Shut down. Closes with 1000, which ends the session for good rather than leaving one to resume. Terminal, and every later input is ignored.
pub fn timer_fired(
bot: Bot(state),
timer: gateway.Timer,
stamp: gateway.Stamp,
) -> Bot(state)
A timer fired. The stamp must be the one that armed it; anything else is a fire that lost the race with its own cancellation and is dropped.
pub fn update(
bot: Bot(state),
with: fn(state) -> state,
) -> Bot(state)
Change your state from outside a handler, for something that did not come from the gateway: a REST response, a tick of your own.
pub fn with_inflater(
bot: Bot(state),
inflater: Inflater(state),
) -> Bot(state)
Turn transport compression on, by handing over the context that makes it
readable. Zlib asks Discord for zlib-stream, Plaintext goes back to
uncompressed: the two are one switch, so neither can be set alone.
pub fn with_seed(bot: Bot(state), seed: Int) -> Bot(state)
Reseed the shard’s jitter. Below 2^31, and different for every shard of a fleet.