Skip to content

SSE transport for incremental delivery (graphql-sse) #33

Closed

Description

@XiNiHa

Problem

Incremental responses ship only as multipart/mixed (greem/src/http.rs:40-100; the axum example negotiates it from Accept). GraphQL over Server-Sent Events (graphql-sse, "distinct connections" mode) is the other transport clients implement for @defer/@stream, and the one that survives proxies and browsers that buffer multipart bodies. accepts_multipart already sees text/event-stream in its test inputs and treats it as not multipart (http.rs:105).

State at 71ccf70

  • The encoder closure of execute_stream (ADR-0008) is where a wire format plugs in. An SSE frame is event: next + data: <json> per payload plus a final event: complete; nothing in the executor changes.
  • The multipart label is incrementalSpec=v0.2 negotiated per Accept (http.rs:45, fc5474d); SSE needs its own negotiation branch.
  • The HTTP response helper (HTTP response helper: own the single-vs-multipart decision in greem::http #21) decides single versus multipart; SSE is a third branch of the same decision and should be designed with it.

Options

Open questions

Blocked by #21.

Origin: Map: greem core, "Not yet specified"; ticket #12.

Activity

  1. XiNiHa commented on Oct 17, 2026

    @XiNiHa
    OwnerAuthor

    Scope note from #40's grilling: subscriptions ship no transport in the core. This issue's distinct-connections mode also carries the subscription response stream (one event: next per event, event: complete when the event source ends), so SSE negotiation in greem::http::handle replaces the 422 a subscription gets today. graphql-ws is #54; Apollo multipart subscriptions is #55 and waits on this negotiation.

  2. added this to the v0.1 milestone on Oct 17, 2026
  3. XiNiHa commented on Oct 17, 2026

    @XiNiHa
    OwnerAuthor

    Settled design (grilling, 2026-10-17)

    Terms Transport and Framing are in CONTEXT.md; the status-code override is ADR-0014.

    Scope

    • graphql-sse distinct connections mode only, as a third framing in greem::http::handle beside a single JSON body and multipart/mixed. It carries @defer/@stream payloads and the subscription response stream (Subscriptions #40).
    • Single connection mode (PUT reservation, X-GraphQL-Event-Stream-Token, 202 + one multiplexed stream, DELETE ?operationId=) is not planned. It needs a reservation table and expiry timers the core does not have (ADR-0008); if ever wanted it is a sibling of graphql-ws transport for subscriptions #54's protocol state machine, not a change to handle.
    • The q-value question is already answered: accept.rs parses q since Serve GraphQL over HTTP from greem::http and audit it with graphql-http #47.

    Negotiation

    • text/event-stream joins accept.rs as a single media type (SSE-only clients, mirroring Media::Multipart) and as a streaming framing. A bare entry matches (Exact); charset other than utf-8 excludes it; other parameters are ignored; text/* and */* do not count, as they do not for multipart.
    • SSE-only clients (no JSON type accepted) get every response as an event stream at 200, request errors included: a parse or validation failure is a next event carrying errors, then complete. graphql-sse requires this; a browser EventSource learns nothing from a 4xx. This overrides ADR-0012 for that client class only (ADR-0014).
    • Mixed-accept clients keep the draft's status codes for single results and request errors; only a response that streams (a subscription, or incremental delivery when SSE is the chosen framing) goes out as events.
    • A subscription streams over SSE whenever text/event-stream is accepted at q > 0, whatever its rank, since nothing else can carry it. Otherwise 406 with a message naming text/event-stream, replacing today's 422.
    • Incremental delivery over SSE follows the multipart rule (preferred at least as much as the single type): one next per payload in the pending/completed format, complete after the hasNext: false payload. When multipart and SSE both qualify: higher q, then higher specificity (incrementalSpec=v0.2 is Format), then multipart on a full tie, as the transport the incremental RFC defines.
    • Subscriptions are allowed over GET (EventSource can only GET) and QUERY; mutations keep 405/422. HTTP-level rejections (405, 406, 415) stay plain status codes for every client: the protocol's "validation steps" are GraphQL's, not method or body checks.

    Surface

    • greem::http gains sse_event(&Payload) -> Vec<u8> (event: next + data: + compact JSON), sse_complete() with the empty data: line EventSource needs, and SSE_CONTENT_TYPE (text/event-stream; charset=utf-8), beside multipart_part and MULTIPART_CONTENT_TYPE.
    • Body::Parts is renamed Body::Stream, framing-neutral. handle chains the complete chunk after the payload stream ends, since the payload encoder never sees the end.
    • Response keeps {status, content_type, allow, body}. Cache-Control: no-cache and SSE comment-line keepalives (:\n\n merged into the body stream on a timer) are adapter concerns, documented on handle and Body::Stream. The axum example sets Cache-Control and adds no keepalive.

    Proof

    • greem/tests/http.rs: SSE-only query as next + complete, request error as events, subscription over GET, incremental delivery over SSE, the multipart-versus-SSE tie, the 406 for a subscription without SSE.
    • The axum example gains a Subscription root with a finite event source over the seeded data, and a test reading a subscription through to complete.
    • http-audit runs the graphql-sse client once against the axum example (a query and a subscription), so a real client's parser accepts the frames.

    #55 (Apollo multipart subscriptions) is unblocked once this lands: it is a second multipart label in the same negotiation, and the multipart-versus-SSE tie-break will then need a subscription case.

  4. added
    ready-for-agentFully specified, ready for an AFK agent
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions