Conventions
A handful of patterns recur across gossamer’s API. None are novel — they follow established Gleam ecosystem idioms — but knowing the conventions makes the binding catalog easier to navigate.
Construction shapes
Configurable bindings take one of three shapes, picked by the argument profile and what the underlying type allows:
- Single function — when every argument is essential (no defaults, no
skippable options). Example:
subtle.encrypt(algorithm, key, data). - Record builder (
gleam_httpstyle) — when the type is a Gleam record carrying stable, reusable identity. Setters useset_<field>(record, field). Example:fetch_extra.options() |> fetch_extra.set_cache(_). - Opaque builder (
glisten/miststyle) — when the configured thing is an opaque JS reference with a mix of required and optional fields. A separateBuilderrecord accumulates fields; a finalizer realizes it. Setters usewith_<field>(builder, field). Example:web_socket.new(uri) |> web_socket.with_on_event(handler) |> web_socket.build.
See gleam_http for the canonical
record-builder precedent.
Per-binding typed errors
Result-returning functions declare a typed error type specific to their
domain. Pattern match concrete variants rather than inspecting a generic dynamic
payload:
use result <- promise.await(subtle.derive_bits(algorithm, key, length))
case result {
Ok(bits) -> use_bits(bits)
Error(crypto.AlgorithmNotSupported) -> fall_back()
Error(crypto.InvalidAccess) -> abort()
Error(_) -> log_and_continue()
}
The variants mirror the JS spec’s failure modes (NotSupportedError,
InvalidAccessError, OperationError, etc.) as named Gleam variants, each type
a closed set.
This follows
gleam/fetch.FetchError
and aligns with the
Gleam conventions doc
on descriptive errors.
Transit types for JS interop
A few bindings (Uint8Array, ArrayBuffer, Map, Set, Iterator,
AsyncIterator) are exposed as JS-native types because some Web APIs return
them and consumers occasionally need to send them through to other JS code.
They’re transit types — for crossing the boundary, not for working on
directly.
Prefer the canonical Gleam type when you control the data flow:
| JS-native (transit) | Canonical Gleam |
|---|---|
Uint8Array / ArrayBuffer | BitArray |
Map | gleam/dict |
Set | gleam/set |
Iterator | gleam_yielder.Yielder |
AsyncIterator | gossamer/async_yielder |
Each transit binding ships from_* / to_* bridges
(uint8_array.to_bit_array, map.from_dict, etc.) so crossing the boundary is
one function call. The canonical Gleam type stays the default for everything
inside your code; reach for the transit type only when a JS API forces you to.
Pure-functional API
The public Gleam API is pure-functional. Setters return new values; where the
underlying JS object is mutable (FormData, for example), it is cloned at the
FFI boundary when mutation would otherwise be observable. Gleam code sees value
semantics:
let form_a =
form_data.new()
|> form_data_extra.append_file("avatar", small_file)
let form_b = form_a |> form_data_extra.append_file("avatar", large_file)
// form_a still has one "avatar" entry; form_b has two
This costs one clone per setter, traded for the consistency of treating gossamer bindings like any other Gleam value. If you specifically need mutation semantics for performance reasons, you can drop down to a custom FFI — but the public Gleam API will never expose observable mutation.
See also
- Runtime gaps — known cross-runtime divergences in the bindings.
- Coverage — what gossamer wraps and what it delegates to ecosystem packages.
- Gleam’s official conventions, patterns, and anti-patterns — the general Gleam idioms gossamer’s patterns build on.