feat(devframe): async generator RPC functions#312
Closed
antfu wants to merge 1 commit into
Closed
Conversation
Add `type: 'generator'` for `defineRpcFunction` so handlers declared as `async function*` stream their yields to the caller without manual channel scaffolding. The framework substitutes the user's definition with an internal action wrapper that allocates a sink on a hidden `devframe:rpc:generators` channel and returns a stream-id envelope; the client wrapper unwraps the envelope and `streaming.subscribe()`s automatically, so `await rpc.call(name, args)` resolves to a ready-to-iterate `StreamReader<Y>`. Cancellation flows through `getCurrentRpcStream()` — an AsyncLocalStorage helper that mirrors `getCurrentRpcSession()` and exposes the sink's `signal`, `streamId`, and originating session inside the handler body. Server-side callers can iterate without paying for the streaming round-trip via `invokeLocalGenerator(rpc, name, ...args)`. Per-stream `replayWindow` defaults to 256 (floored to 1) to win the client-subscribe-vs-first-yield race; this required threading the existing `RpcStreamingChannel.start()` helper to accept a per-stream override on top of the channel default. Generator definitions reject `agent`, `cacheable`, `dump`, `snapshot`, and `jsonSerializable: true` at registration via four new diagnostics (DF0033–DF0036). DF0034 fires at runtime if the handler doesn't return an `AsyncIterable`. Also fixes a pre-existing replay bug in `node/rpc-streaming.ts`: when a producer closed with an error and a subscriber arrived during the `closedStreamRetention` window, the late subscriber received a clean close instead of the original error. The streaming record now captures the end payload and replays it on subscribe. Migrations & dogfooding: - Streaming guide gains an "Async Generator RPC" section ahead of the manual channel section, framed as the recommended path. - `devframe-streaming-chat` example adds a `:tokenize` generator alongside the existing `:send` action so readers can compare both approaches in one place. - `skills/devframe` and `skills/vite-devtools-kit` get matching guidance. - 12 new integration tests (`rpc-generators.test.ts`) covering happy path, cooperative cancel, throw mid-stream, throw before first yield, late-subscriber replay, concurrent isolation, `invokeLocalGenerator`, and all four validation diagnostics. tsnapi snapshots updated for the new public exports (`getCurrentRpcStream`, `invokeLocalGenerator`, `attachRpcGenerators`, `RpcGeneratorStreamContext`, `RpcGeneratorFunctionDefinition`). Pre-PR checklist (lint + 462 tests + typecheck + build) all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
type: 'generator'todefineRpcFunctionso handlers declared asasync function*stream their yields to the client without manual channel scaffolding. The framework substitutes the user's definition with an internal action wrapper that allocates a sink on a hiddendevframe:rpc:generatorschannel and returns an envelope; the client wrapper unwraps it andstreaming.subscribe()s automatically, soawait rpc.call(...)resolves to a ready-to-iterateStreamReader<Y>. Cancellation flows throughgetCurrentRpcStream()— an AsyncLocalStorage helper that mirrorsgetCurrentRpcSession()— andinvokeLocalGenerator(rpc, name, ...args)lets server-side callers iterate without paying for the streaming round-trip. Generator definitions rejectagent,cacheable,dump,snapshot, andjsonSerializable: trueat registration via four new diagnostics (DF0033–DF0036). Also fixes a pre-existing replay bug innode/rpc-streaming.ts: when a producer closed with an error and a subscriber arrived during theclosedStreamRetentionwindow, the late subscriber received a clean close instead of the original error — the streaming record now captures and replays the end payload.Linked Issues
Follow-up to #307. Builds the higher-level ergonomic surface that the streaming-channel API was designed to enable.
Additional context
rpc-generators.test.tsadds 12 integration tests covering happy path, cooperative cancel viagetCurrentRpcStream().signal, throw mid-stream, throw before first yield, late-subscriber replay, concurrent isolation,invokeLocalGenerator, and all four validation diagnostics.skills/devframeandskills/vite-devtools-kitget matching guidance.devframe-streaming-chatexample adds a:tokenizegenerator alongside the existing:sendaction so readers can compare both approaches.replayWindowdefaults to 256 (floored to 1) to win the client-subscribe-vs-first-yield race; this required threadingRpcStreamingChannel.start()to accept a per-stream override on top of the channel default.agentexposure for generators, bidirectional generators, generator return values.🤖 Generated with Claude Code