Skip to content

Expire sessions with a Cache

Resolve a session token to its user for a bounded lifetime, expiring entries automatically without writing a sweep, and report how many sessions are currently live.

commons tokens
---
A session token — an opaque, bounded handle that addresses a cache entry. It is
non-empty and length-capped, so a malformed token is rejected at the boundary
before any cache lookup runs.
---
type Token = String where NonEmpty && MaxLength(128)
---
The authenticated user a live token resolves to.
---
type UserId = String where NonEmpty
Open the full project ↗

This example reaches Workers-only shapes (storage bindings, agents, or cron), so it runs with bynk dev rather than in the browser playground. See Install to get started.

agent Sessions is keyed by a shard string and holds one store: live: Cache[Token, UserId] @ttl(30.minutes). A Cache is a Map whose entries expire on their own — each put (re)starts the 30-minute clock, an entry past its TTL reads as None, and expired entries are reaped at the next commit, so size counts only live entries with no separate sweep to write. The boundary types are refined in commons tokens: Token is NonEmpty && MaxLength(128), UserId is NonEmpty, so a malformed token is rejected before any cache lookup.

Time is honest. Eviction consults the clock, so every cache operation except remove declares given Clocklogin, whoami, and active all carry it, while logout (an idempotent live.remove) does not. The dependency is visible in each signature, and a mocked clock would make expiry deterministic.

The HTTP service exposes POST /sessions (login, returns Created("ok")), GET /sessions/:token (whoami — Some resolves to the user, None is NotFound), POST /sessions/:token/logout (NoContent), and GET /sessions (the live count). Every route delegates to Sessions("default").