columbo
Provides SSR suspense capabilities. Render a placeholder for a future, and stream the replacement elements.
Called columbo because Columbo always said, "And another thing..."
For the purposes of this library, the verb
suspendgenerally means "defer the rendering and sending of an async workload", in the context of rendering a web document.
Overview
The entrypoint for the library is the [new()] function, which returns a
[SuspenseContext] and a [SuspendedResponse]. The [SuspenseContext]
allows you to suspend() futures to be sent
down the stream when they are completed, wrapped with just enough HTML to be
interpolated into wherever the resulting [Suspense] struct was rendered
into the document as a placeholder. [SuspendedResponse] acts as a receiver
for these suspended results. When done rendering your document, pass it your
document and call into_stream() to get
seamless SSR streaming suspense.
So in summary:
- Use [
SuspenseContext] to callsuspend()and suspend futures. - Call
into_stream()to setup your response stream.
The suspend() function provides access to
itself for the futures it suspends by taking a closure returning a future,
so futures can spawn additional suspensions or listen for cancellation.
Responses are streamed in completion order, not registration order, so the future that completes first will stream first.
Cancel Safety
By default, if [SuspendedResponse] or the type resulting from
into_stream() are dropped, the futures
that have been suspended will continue to run, but their results will be
inaccessible. If you would like for tasks to cancel instead, you can enable
auto_cancel in [ColumboOptions], or you can use
cancelled() or
is_cancelled() to exit early from within
the future.
Axum Example
use ;
async
Use [new_with_opts] to configure columbo behavior:
use Any;
use ;
use ;
async
Architecture
Internally, [SuspenseContext] holds a channel sender. When
suspend() is called, it launches a task which
runs the given future to completion. The result of this future (or a panic
message if it panicked) is wrapped in a <template> tag and sent as a
message to the channel. A single global <script> is injected once into
the initial document chunk and handles swapping each <template> into its
placeholder.
[SuspendedResponse] contains a receiver. It just sits around until you
call into_stream(), at which point the
receiver is turned into a stream whose elements are preceded by the
document you provide.