glimr/session/session
Session
Gleam is immutable, but sessions need mutable state that persists across multiple reads and writes within a single request handler. An OTP actor per request provides that mutability safely — each operation is a message, so concurrent access is serialized. The middleware reads the actor’s final state after the handler returns to persist only what actually changed, avoiding unnecessary store writes.
Types
Context requires a Session field at construction time, but the actor can’t start until a request arrives with cookie data. The Empty variant satisfies the type system during boot without allocating an actor, while Live wraps the real per-request actor subject. Making the type opaque prevents callers from pattern matching on the variant and coupling to the internal representation.
pub opaque type Session
Values
pub fn all(session: Session) -> dict.Dict(String, String)
Bulk reads are needed for serialization (e.g. the cookie store encoding the full session into a signed cookie) and for debugging. Returns a snapshot — further mutations after this call won’t be reflected in the returned dict.
pub fn cookie_store() -> store.SessionStore
Cookie-based sessions avoid server-side storage entirely — the signed cookie is the store. This is ideal for small payloads under ~4KB where the simplicity of zero infrastructure outweighs the per-request bandwidth cost.
pub fn empty() -> Session
Context is constructed at boot before any HTTP request arrives, so there’s no cookie data to seed a real session actor. This returns a no-op handle where all reads return empty values and all writes are silently ignored, avoiding the need for Option(Session) throughout the framework.
pub fn error(session: Session, field: String) -> String
Showing inline validation errors next to the field that failed is way better than a generic “something went wrong” banner. The validator flashes each field’s first error so templates can display it right where it matters.
<p l-if="session.error(ctx.session, 'email') != ''" class="text-red-600">
{{ session.error(ctx.session, "email") }}
</p>
pub fn flash(session: Session, key: String, value: String) -> Nil
Flash messages provide one-shot feedback across redirects (e.g. “Item saved successfully”). Storing them separately from regular session data and clearing them after one read ensures they appear exactly once without the handler needing to manage cleanup.
pub fn forget(session: Session, key: String) -> Nil
Removing a key marks the session dirty so the middleware persists the deletion. Fire-and-forget like put since the caller doesn’t need to wait for confirmation.
pub fn get(session: Session, key: String) -> Result(String, Nil)
Reads go through actor.call (synchronous) because the caller needs the value immediately to make decisions in the handler. Returns Error(Nil) for missing keys so callers can distinguish absence from an empty string.
pub fn get_flash(session: Session, key: String) -> String
Most flash reads happen in templates where an empty string is the natural “no flash” value. Returning “” instead of a Result avoids wrapping every template interpolation in a case expression.
pub fn get_flash_or(
session: Session,
key: String,
) -> Result(String, Nil)
When the handler needs to distinguish between “no flash was set” and “flash was set to an empty string”, this Result- returning variant provides that distinction. get_flash delegates here and unwraps for the common case.
pub fn has(session: Session, key: String) -> Bool
Existence checks are synchronous (actor.call) because the caller typically branches on the result immediately, e.g. to decide whether to redirect an unauthenticated user.
pub fn has_error(session: Session, field: String) -> Bool
Templates often need to conditionally show error styling or
error messages. Checking error() != "" works but reads
awkwardly in template expressions. This gives a clean
boolean for l-if directives.
<p l-if="session.has_error(ctx.session, 'email')" class="text-red-600">
{{ session.error(ctx.session, "email") }}
</p>
pub fn has_flash(session: Session, key: String) -> Bool
Templates often need to conditionally render a flash banner only when a message exists. A Bool check is cleaner than matching on a Result when the value itself isn’t needed for the conditional.
pub fn id(session: Session) -> String
The session ID is needed by the store to look up or persist the session data. Exposing it lets middleware and store implementations access it without reaching into the actor’s internal state directly.
pub fn invalidate(session: Session) -> Nil
Logout and account deletion need to destroy all session state and issue a new ID so the old session cookie can never be reused. The invalidated flag tells middleware to delete the old entry from the store rather than just overwriting it.
pub fn old(session: Session, field: String) -> String
Nobody wants to retype an entire form because one field failed validation. After a redirect back, the validator stashes the old values as flash data so templates can repopulate inputs automatically.
<input type="email" name="email" :value="session.old(ctx.session, 'email')" />
pub fn put(session: Session, key: String, value: String) -> Nil
Writes use process.send (fire-and-forget) because the caller doesn’t need confirmation — the actor serializes all mutations and the middleware reads the final state after the handler returns.
pub fn regenerate(session: Session) -> Nil
After login, the session ID must change to prevent session fixation attacks — an attacker who planted a known session ID before authentication can’t hijack the post-login session if the ID rotates. Data is preserved so the user doesn’t lose pre-login state.
pub fn setup(session_store: store.SessionStore) -> Nil
Caches the given session store in persistent_term so the session middleware can access it on every request without threading it through function arguments. Call this once at boot after creating a store from any driver.