Streams of a room: output that a process writes a piece at a time (a model's answer, an agent's report, an import's log), that people see as it comes, and find again when they come back: from another page, another tab, after their connection dropped, on another node.
{:ok, stream} =
Issho.Streams.start(MyApp.Issho, "notebook:7",
kind: "summary",
for: ada,
place: %{"doc" => "notebook:7/intro", "at" => %{"node" => block_id}},
by: claude
)
:ok = Issho.Streams.append(stream, "## Summary\n\n")
:ok = Issho.Streams.append(stream, "The plan holds…")
:ok = Issho.Streams.finish(stream)Issho keeps a stream's chunks and sends them on; it does not read them. What a stream is, each component or app decides: a suggestion in a text, a card waiting on a canvas, a chat's answer, a log.
Who sees it
:for is required: a person (an actor, or a key), who sees it, and so
do their agents (on_behalf_of); :room, everyone the policy lets
read the room; or :policy, whomever the policy allows the action
:read_stream in the room. Its writer (:by) sees it too. Every reader
may read the room (:read).
Seen as it comes, found again
subscribe/4 gives the stream as it is now, its content so far, and
from then on sends the subscriber what comes, in order, with nothing
missed or sent twice:
{Issho, :stream, id, {:chunk, data}}{Issho, :stream, id, :finished}{Issho, :stream, id, {:failed, reason}}{Issho, :stream, id, {:closed, outcome}}{Issho, :stream, id, :gone}: kept no longer.
watch/3 subscribes to every stream of a room the caller may see, and
to the news of new ones: {Issho, :stream_started, room, stream}, on
which the watcher calls subscribe/4 (it is refused a stream it may not
see). A LiveView does it with Issho.Live.streams/1, which does both.
Its life
A stream lives while it is written. Finished (finish/1) or failed
(fail/2), it is kept for :keep milliseconds (the instance's
:stream_keep, five minutes), for those who come back, then goes.
close/2 ends it at once, with an outcome the app names ("accepted",
"rejected", "stopped", its own): a suggestion taken or turned down.
Its writer is the process that starts it (or :writer): when it exits
while the stream is written, the stream fails ("writer_exited"), or
finishes with on_exit: :finish. No stream is left being written.
Each start, finish, failure and close is an event of the room
(Issho.Event): "stream.started", "stream.finished",
"stream.failed" and "stream.closed", with data %{stream: id, kind: kind, for: for} and the reason or outcome; so an agent learns
whether its draft was taken. Their content is not in them.
Limits
A stream holds at most the instance's :stream_max_bytes (1 MB): one
that would hold more fails ("too_large"). A room has at most
:room_max_streams (32) at once. Streams are in memory, as presence
is: a stream does not outlive its node.
Each stream is a process (:gen_statem), named in the whole cluster
(:global), so it is found from any node; the streams of a room are a
process group (:pg).
Summary
Functions
Adds a chunk (a binary) to the stream. {:error, :not_writing} once it
is done; {:error, :too_large} when the stream would hold more than its
limit, and it fails.
Closes the stream now, with an outcome the app names (:accepted,
:rejected, :stopped, its own): it goes, and its subscribers hear
how. From a person's page, Issho.Live.close_stream/3 checks they may
see it first.
Fails the stream, with a reason (a string or an atom).
Finishes the stream: it is complete, and kept for those who come back.
The stream as it is now: {:ok, stream} or {:error, :not_found}.
The streams of a room that who (an actor or an identity) may see, as
they are now, oldest first.
Starts a stream in a room, written by the caller (or :writer).
Options: :for (required), :kind, :place, :meta, :by (an actor
or an identity: who writes it, as the room sees them), :writer,
:keep (milliseconds), :on_exit (:fail or :finish).
Subscribes pid (the caller) to a stream that who may see: gives the
stream as it is now, then sends what comes (see above).
{:error, :forbidden} for one they may not see.
Stops sending a stream to pid (the caller).
Stops the news of a room's new streams (watch/3).
Whether who (an identity) may see the stream: they may read its room,
and it is for them (or for whom they act), for the room, for whom the
policy allows :read_stream, or they write it.
Subscribes the caller to every stream of the room that who may see,
and to the news of new ones ({Issho, :stream_started, room, stream}).
Gives the streams, as they are now.
Types
@type ref() :: Issho.Stream.t() | {atom(), String.t()}
Functions
Adds a chunk (a binary) to the stream. {:error, :not_writing} once it
is done; {:error, :too_large} when the stream would hold more than its
limit, and it fails.
Closes the stream now, with an outcome the app names (:accepted,
:rejected, :stopped, its own): it goes, and its subscribers hear
how. From a person's page, Issho.Live.close_stream/3 checks they may
see it first.
Fails the stream, with a reason (a string or an atom).
Finishes the stream: it is complete, and kept for those who come back.
@spec get(atom(), String.t()) :: {:ok, Issho.Stream.t()} | {:error, :not_found}
The stream as it is now: {:ok, stream} or {:error, :not_found}.
@spec list(atom(), String.t(), Issho.Actor.t() | Issho.Identity.t()) :: [ Issho.Stream.t() ]
The streams of a room that who (an actor or an identity) may see, as
they are now, oldest first.
@spec start(atom(), String.t(), keyword()) :: {:ok, Issho.Stream.t()} | {:error, term()}
Starts a stream in a room, written by the caller (or :writer).
Options: :for (required), :kind, :place, :meta, :by (an actor
or an identity: who writes it, as the room sees them), :writer,
:keep (milliseconds), :on_exit (:fail or :finish).
@spec subscribe( atom(), String.t() | Issho.Stream.t(), Issho.Actor.t() | Issho.Identity.t(), pid() ) :: {:ok, Issho.Stream.t()} | {:error, :forbidden | :not_found}
Subscribes pid (the caller) to a stream that who may see: gives the
stream as it is now, then sends what comes (see above).
{:error, :forbidden} for one they may not see.
Stops sending a stream to pid (the caller).
Stops the news of a room's new streams (watch/3).
@spec visible?(Issho.Stream.t(), Issho.Identity.t()) :: boolean()
Whether who (an identity) may see the stream: they may read its room,
and it is for them (or for whom they act), for the room, for whom the
policy allows :read_stream, or they write it.
@spec watch(atom(), String.t(), Issho.Actor.t() | Issho.Identity.t()) :: {:ok, [Issho.Stream.t()]}
Subscribes the caller to every stream of the room that who may see,
and to the news of new ones ({Issho, :stream_started, room, stream}).
Gives the streams, as they are now.