Stream from an async generator
Make the handler of a slash command or message action anasync function*. Each yield is an answer, the same shape a handler returns. The generator’s
return value, if it has one, is the last update.
src/deployCommand.tsx
Deploy.insert checks its props and the app must
include Deploy. If the generator throws after its first answer, the SDK finishes the answer with
the failure notice, “Couldn’t finish this. Try again.”, and logs the error.
Stream from an Effect
On the Effect API, return aStream of answers. Services and failures in the stream become the
command’s requirements and errors. See Progress updates
in the Effect guide.
How the answer moves
The SDK starts the generator or stream when the delivery arrives:- If the first answer comes within 3 seconds, it’s the delivery’s answer, the same as a normal handler’s.
- If it’s slower, the SDK answers the delivery with
202and your app’s row reads “is thinking…” until the first answer arrives. Only the member who ran the command sees that row. Passprogress: { visibility: "public" }to the definition to show it to everyone in the circle or direct message. - Every later answer is sent as an update. The SDK waits 500ms after an answer arrives, then sends the latest one, so a generator that yields faster than that skips the stages in between.
- When the generator returns or the stream ends within 500ms of its last value, the SDK sends that value once, as the final update. If the stream ends later, that value has already gone out as an update, so the SDK sends it again as the final update to close the answer. The answer can’t change after that.
waitUntil. Hosted runtimes keep
that work alive for a limited time, so hand work that runs for many minutes to a queue and update the
answer from there, as described in Update from somewhere else.
Blocks and visibility
The first answer decides the visibility. A later answer withcontent or a block repeats the same
visibility, and so does the generator’s return; the types require it, and the SDK rejects one that
leaves it out. A later answer that’s only a notice keeps the first answer’s visibility. Changing it
is an error.
A later answer’s block must come from the same block definition as the first answer’s block. Its
props replace the block’s props and the block renders again. Leave block out to keep the block as it
is. Switching to another block, or adding a block when the first answer had none, is an error.
When the stream breaks either rule, the SDK stops it, logs ReintersectAppProgressMismatchError
(a defect on the Effect API), and finishes the answer with the failure notice.
Limits
There’s no limit on the number of updates.
When the 2 hours pass, or 15 minutes go by without an update, Reintersect stops taking text and notice
changes. If your stream keeps going, the SDK keeps sending its block changes on their own, so the
block stays current while your app reports at least every 15 minutes. These updates need no extra
scope, because the member’s command created the block. The answer ends when the stream does.
Errors
Update from somewhere else
When a queue worker or another service does the work, defer the answer, passinvocation.interactionToken along, and send updates with the app client. Answer first with
invocations.respond, then call invocations.update, with final: true on the last one:
src/deployWorker.ts
POST /v0/invocations/update with the install token and
{ interactionToken, answer, final? }.
Testing
host.runCommand returns the first answer, or { status: "deferred" } when the first answer took
longer than the 3-second budget on the test clock. While your handler waits on the test clock, such as
an Effect.sleep, the clock jumps ahead; while it waits on real work, such as a fetch, the clock
keeps pace with real time, so only work that really takes 3 seconds is deferred. host.settle()
runs the stream to its end and moves the test clock through the SDK’s waits.
host.answersOf(interactionToken) returns every answer the member saw, in order, and whether the
answer is final. host.expectProgression checks that answers matching each step arrived in that
order, with any other answers in between, and that the answer finished. A stage that coalescing
skipped never reaches the member, so a step that names it fails, and the failure says which step
went missing. Name only the stages that last at least 500ms, such as the first answer and the final
one:
invocations.update push with its final flag.