Liking cljdoc? Tell your friends :D

co.multiply.tropical.session

Tab sessions: one island tree per browser tab, outliving any single SSE connection.

A session is created when the page is served, and the page is rendered from its first frame. The tab's stream (ring/stream-init) then attaches to it, and the session belongs to that page load from then on. Datastar drops that stream routinely: a hidden tab closes it, network blips retry it. A detached session keeps its islands, and so their resources, for grace-ms. A reconnect within that window gets what the page lacks, without reopening anything: what changed since, if the last event id it read is the mark that followed what it was sent (sse/mark!), and otherwise the whole cached frame. After the window, every island is unmounted and everything they held is released.

Each session is run by one pump: a virtual thread that owns all of the session's mutable state, its island runtime included, and consumes the session's inbox (co.multiply.tropical.signal.Inbox). The inbox holds lifecycle events (attach, detach, close) and the subscriptions marked since the pump last looked. A change to something the islands read only marks its subscription, once until the pump takes it. Rendering happens on the pump, never on the thread that made the change, so a shared resource's loop never renders on anyone's behalf. Changes that arrive while a frame is being sent coalesce into the next one, so a slow client gets fewer, later frames, never a backlog, and the inbox never holds more than one mark per subscription.

A session runs an app, a map:

  • :root (fn [{:keys [tab uid request]}] call): the session's root island, given the session's context, which every island reads with use-session. :request is the request that created the session, where co.multiply.tropical.ring created it.
  • :patch-signals optional, (fn [info] signals): a map of signals to send with each patch, or nil. See patch-signals.
  • :error-view optional, (fn [failure] hiccup): what renders in place of an island whose render throws, unless the island has a view of its own (see island/error-view).
  • :first-frame-ms optional: how long, in ms, the session's first frame may wait for reads still pending (below). Defaults to 0.
  • :stream-max-ms optional: how long, in ms, the tab's stream stays open before the session ends it, and Datastar reconnects it at once, to this session, which sends what changed meanwhile, since the page has the rest. Off by default. For a balancer whose stickiness lapses, such as a cookie that lasts ten minutes: set under that, each reconnect passes the balancer while the tab is open, and renews it if each request extends the cookie's life, not if its life counts from when it was set. A stream ends at a random point in the last tenth of it, so streams opened together don't reconnect together.

The first frame a session delivers, to the page or over the stream that rebuilt the session, is the first a client sees from it. Its reads are often cold: a new tab, a first visit, every open tab after a restart. Sent at once, it shows their pending states, which the data replaces a moment later. So it waits until no read of its islands is pending, or :first-frame-ms passes, and goes out as it is then. It renders as reads land meanwhile, so islands that appear only once an earlier read lands get to read too. Later frames don't wait. A page waits at most 5 s for its frame (render-page!), so keep the wait well under that: 100 to 200 ms covers the reads worth waiting for.

Scripts the islands declare (co.multiply.tropical.script) go over the stream once each, appended to the page's head, ahead of the first frame that needs them: those of the first frame too, unless the page put them in its head (scripts-in-page!), in case it left them out. A session belongs to one page load, which keeps the modules it loaded, so a reconnect doesn't send them again.

A session whose pump fails, because a step outside the islands' renders threw, is closed, and so is its stream: the client reconnects into a new session instead of waiting on one that sends nothing.

Tab sessions: one island tree per browser tab, outliving any single SSE
connection.

A session is created when the page is served, and the page is rendered from
its first frame. The tab's stream (`ring/stream-init`) then attaches to it,
and the session belongs to that page load from then on. Datastar drops that
stream routinely: a hidden tab closes it, network blips retry it.
A detached session keeps its islands, and so their resources, for `grace-ms`.
A reconnect within that window gets what the page lacks, without reopening
anything: what changed since, if the last event id it read is the mark that
followed what it was sent (`sse/mark!`), and otherwise the whole cached
frame. After the window, every island is unmounted and everything they held
is released.

Each session is run by one pump: a virtual thread that owns all of the
session's mutable state, its island runtime included, and consumes the
session's inbox (`co.multiply.tropical.signal.Inbox`). The inbox holds
lifecycle events (attach, detach, close) and the subscriptions marked since
the pump last looked. A change to something the islands read only marks its subscription,
once until the pump takes it. Rendering happens on the pump, never on the
thread that made the change, so a shared resource's loop never renders on
anyone's behalf. Changes that arrive while a frame is being sent coalesce into
the next one, so a slow client gets fewer, later frames, never a backlog, and
the inbox never holds more than one mark per subscription.

A session runs an app, a map:

- `:root`           `(fn [{:keys [tab uid request]}] call)`: the session's root
                    island, given the session's context, which every island
                    reads with `use-session`. `:request` is the request that
                    created the session, where `co.multiply.tropical.ring`
                    created it.
- `:patch-signals`  optional, `(fn [info] signals)`: a map of signals to send
                    with each patch, or nil. See `patch-signals`.
- `:error-view`     optional, `(fn [failure] hiccup)`: what renders in place
                    of an island whose render throws, unless the island has
                    a view of its own (see `island/error-view`).
- `:first-frame-ms` optional: how long, in ms, the session's first frame may
                    wait for reads still pending (below). Defaults to 0.
- `:stream-max-ms`  optional: how long, in ms, the tab's stream stays open
                    before the session ends it, and Datastar reconnects it
                    at once, to this session, which sends what changed
                    meanwhile, since the page has the rest. Off by default.
                    For a balancer whose stickiness lapses, such as a
                    cookie that lasts ten minutes: set under that, each
                    reconnect passes the balancer while the tab is open,
                    and renews it if each request extends the cookie's
                    life, not if its life counts from when it was set.
                    A stream ends at a random point in the last tenth of
                    it, so streams opened together don't reconnect
                    together.

The first frame a session delivers, to the page or over the stream that
rebuilt the session, is the first a client sees from it. Its reads are often
cold: a new tab, a first visit, every open tab after a restart. Sent at once,
it shows their pending states, which the data replaces a moment later. So it
waits until no read of its islands is pending, or `:first-frame-ms` passes,
and goes out as it is then. It renders as reads land meanwhile, so islands
that appear only once an earlier read lands get to read too. Later frames
don't wait. A page waits at most 5 s for its frame (`render-page!`), so keep
the wait well under that: 100 to 200 ms covers the reads worth waiting for.

Scripts the islands declare (`co.multiply.tropical.script`) go over the
stream once each, appended to the page's head, ahead of the first frame that
needs them: those of the first frame too, unless the page put them in its
head (`scripts-in-page!`), in case it left them out.
A session belongs to one page load, which keeps the modules it loaded, so a
reconnect doesn't send them again.

A session whose pump fails, because a step outside the islands' renders
threw, is closed, and so is its stream: the client reconnects into a new
session instead of waiting on one that sends nothing.
raw docstring

!sessionsclj

The open sessions, for display: an atom of a map keyed by tab, which changes only on a session's lifecycle events. Watch it through summary.

The open sessions, for display: an atom of a map keyed by tab,
which changes only on a session's lifecycle events. Watch it through
`summary`.
sourceraw docstring

close-all!clj

(close-all!)

Closes every session, as after reloading code in development. Each tab's stream ends, and Datastar reconnects about a second later into a fresh session, rendered by the code loaded then, whose first frame updates the page in place.

Call it once the reloaded code serves requests: a tab that reconnects before that gets a session of the old code. What only a page load renders, such as the page's head or an edited script, shows once the page is reloaded.

Closes every session, as after reloading code in development. Each tab's
stream ends, and Datastar reconnects about a second later into a fresh
session, rendered by the code loaded then, whose first frame updates the page
in place.

Call it once the reloaded code serves requests: a tab that reconnects before
that gets a session of the old code. What only a page load renders, such as
the page's head or an edited script, shows once the page is reloaded.
sourceraw docstring

close-user!clj

(close-user! uid)

Closes every session of the user uid in this JVM, as when they sign out or are removed: their islands unmount, their actions are revoked, and their tabs' streams end. Datastar reconnects each about a second later, through the route's middleware. Once the app has ended the user's sign-in, that refuses the stream, and ring/wrap-refused-streams reloads the page into the app's own answer, such as its sign-in page. A user still signed in reconnects into a fresh session.

End the sign-in first: a tab that reconnects before that gets a fresh session. A request that passed the middleware before the sign-in ended, and creates its session after this, keeps it. With several servers, each closes its own sessions, so the app tells every one of them. The Security guide has an island that follows the user's standing instead, which covers both.

Closes every session of the user `uid` in this JVM, as when they sign out or
are removed: their islands unmount, their actions are revoked, and their
tabs' streams end. Datastar reconnects each about a second later, through the
route's middleware. Once the app has ended the user's sign-in, that refuses
the stream, and `ring/wrap-refused-streams` reloads the page into the app's
own answer, such as its sign-in page. A user still signed in reconnects into
a fresh session.

End the sign-in first: a tab that reconnects before that gets a fresh session.
A request that passed the middleware before the sign-in ended, and creates
its session after this, keeps it. With several servers, each closes its own
sessions, so the app tells every one of them. The Security guide has an
island that follows the user's standing instead, which covers both.
sourceraw docstring

frame-msclj

The least time, in ms, between a session's frames. Changes within it coalesce into the next frame, and the latest wins.

The least time, in ms, between a session's frames. Changes within it
coalesce into the next frame, and the latest wins.
sourceraw docstring

grace-msclj

How long, in ms, a session whose stream went away keeps its islands, and what they hold, for the tab to reconnect, before it closes.

How long, in ms, a session whose stream went away keeps its islands, and
what they hold, for the tab to reconnect, before it closes.
sourceraw docstring

heartbeat-msclj

How long, in ms, a quiet stream goes before it writes a heartbeat, an SSE comment: a dead connection is noticed only on write, and a proxy's idle timeout would close a live one.

How long, in ms, a quiet stream goes before it writes a heartbeat, an SSE
comment: a dead connection is noticed only on write, and a proxy's idle
timeout would close a live one.
sourceraw docstring

summaryclj

(summary)
(summary sessions)

How many sessions have their stream, and how many are waiting for it, as {:attached :detached}. Of every session, or of sessions, a value !sessions held.

How many sessions have their stream, and how many are waiting for it, as
`{:attached :detached}`. Of every session, or of `sessions`, a value
`!sessions` held.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close